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 vnextroot
86 global lookingforhead showlocalchanges
88 set startmsecs [clock clicks -milliseconds]
89 set commitidx($view) 0
90 set vnextroot($view) 0
91 set order "--topo-order"
93 set order "--date-order"
96 set fd [open [concat | git log -z --pretty=raw $order --parents \
97 --boundary $viewargs($view) "--" $viewfiles($view)] r]
99 error_popup "Error executing git rev-list: $err"
102 set commfd($view) $fd
103 set leftover($view) {}
104 set lookingforhead $showlocalchanges
105 fconfigure $fd -blocking 0 -translation lf -eofchar {}
106 if {$tclencoding != {}} {
107 fconfigure $fd -encoding $tclencoding
109 filerun $fd [list getcommitlines $fd $view]
113 proc stop_rev_list {} {
114 global commfd curview
116 if {![info exists commfd($curview)]} return
117 set fd $commfd($curview)
123 unset commfd($curview)
127 global phase canv mainfont curview
131 start_rev_list $curview
132 show_status "Reading commits..."
135 # This makes a string representation of a positive integer which
136 # sorts as a string in numerical order
139 return [format "%x" $n]
140 } elseif {$n < 256} {
141 return [format "x%.2x" $n]
142 } elseif {$n < 65536} {
143 return [format "y%.4x" $n]
145 return [format "z%.8x" $n]
148 proc getcommitlines {fd view} {
150 global leftover commfd
151 global displayorder commitidx commitrow commitdata
152 global parentlist children curview hlview
153 global vparentlist vdisporder vcmitlisted
154 global ordertok vnextroot
156 set stuff [read $fd 500000]
157 # git log doesn't terminate the last commit with a null...
158 if {$stuff == {} && $leftover($view) ne {} && [eof $fd]} {
168 # set it blocking so we wait for the process to terminate
169 fconfigure $fd -blocking 1
170 if {[catch {close $fd} err]} {
172 if {$view != $curview} {
173 set fv " for the \"$viewname($view)\" view"
175 if {[string range $err 0 4] == "usage"} {
176 set err "Gitk: error reading commits$fv:\
177 bad arguments to git rev-list."
178 if {$viewname($view) eq "Command line"} {
180 " (Note: arguments to gitk are passed to git rev-list\
181 to allow selection of commits to be displayed.)"
184 set err "Error reading commits$fv: $err"
188 if {$view == $curview} {
189 run chewcommits $view
196 set i [string first "\0" $stuff $start]
198 append leftover($view) [string range $stuff $start end]
202 set cmit $leftover($view)
203 append cmit [string range $stuff 0 [expr {$i - 1}]]
204 set leftover($view) {}
206 set cmit [string range $stuff $start [expr {$i - 1}]]
208 set start [expr {$i + 1}]
209 set j [string first "\n" $cmit]
212 if {$j >= 0 && [string match "commit *" $cmit]} {
213 set ids [string range $cmit 7 [expr {$j - 1}]]
214 if {[string match {[-<>]*} $ids]} {
215 switch -- [string index $ids 0] {
220 set ids [string range $ids 1 end]
224 if {[string length $id] != 40} {
232 if {[string length $shortcmit] > 80} {
233 set shortcmit "[string range $shortcmit 0 80]..."
235 error_popup "Can't parse git log output: {$shortcmit}"
238 set id [lindex $ids 0]
239 if {![info exists ordertok($view,$id)]} {
240 set otok "o[strrep $vnextroot($view)]"
241 incr vnextroot($view)
242 set ordertok($view,$id) $otok
244 set otok $ordertok($view,$id)
247 set olds [lrange $ids 1 end]
248 if {[llength $olds] == 1} {
249 set p [lindex $olds 0]
250 lappend children($view,$p) $id
251 if {![info exists ordertok($view,$p)]} {
252 set ordertok($view,$p) $ordertok($view,$id)
257 if {$i == 0 || [lsearch -exact $olds $p] >= $i} {
258 lappend children($view,$p) $id
260 if {![info exists ordertok($view,$p)]} {
261 set ordertok($view,$p) "$otok[strrep $i]]"
269 if {![info exists children($view,$id)]} {
270 set children($view,$id) {}
272 set commitdata($id) [string range $cmit [expr {$j + 1}] end]
273 set commitrow($view,$id) $commitidx($view)
274 incr commitidx($view)
275 if {$view == $curview} {
276 lappend parentlist $olds
277 lappend displayorder $id
278 lappend commitlisted $listed
280 lappend vparentlist($view) $olds
281 lappend vdisporder($view) $id
282 lappend vcmitlisted($view) $listed
287 run chewcommits $view
292 proc chewcommits {view} {
293 global curview hlview commfd
294 global selectedline pending_select
297 if {$view == $curview} {
298 set allread [expr {![info exists commfd($view)]}]
299 set tlimit [expr {[clock clicks -milliseconds] + 50}]
300 set more [layoutmore $tlimit $allread]
301 if {$allread && !$more} {
302 global displayorder commitidx phase
303 global numcommits startmsecs
305 if {[info exists pending_select]} {
306 set row [first_real_row]
309 if {$commitidx($curview) > 0} {
310 #set ms [expr {[clock clicks -milliseconds] - $startmsecs}]
311 #puts "overall $ms ms for $numcommits commits"
313 show_status "No commits selected"
319 if {[info exists hlview] && $view == $hlview} {
325 proc readcommit {id} {
326 if {[catch {set contents [exec git cat-file commit $id]}]} return
327 parsecommit $id $contents 0
330 proc updatecommits {} {
331 global viewdata curview phase displayorder
332 global children commitrow selectedline thickerline
339 foreach id $displayorder {
340 catch {unset children($n,$id)}
341 catch {unset commitrow($n,$id)}
344 catch {unset selectedline}
345 catch {unset thickerline}
346 catch {unset viewdata($n)}
353 proc parsecommit {id contents listed} {
354 global commitinfo cdate
363 set hdrend [string first "\n\n" $contents]
365 # should never happen...
366 set hdrend [string length $contents]
368 set header [string range $contents 0 [expr {$hdrend - 1}]]
369 set comment [string range $contents [expr {$hdrend + 2}] end]
370 foreach line [split $header "\n"] {
371 set tag [lindex $line 0]
372 if {$tag == "author"} {
373 set audate [lindex $line end-1]
374 set auname [lrange $line 1 end-2]
375 } elseif {$tag == "committer"} {
376 set comdate [lindex $line end-1]
377 set comname [lrange $line 1 end-2]
381 # take the first non-blank line of the comment as the headline
382 set headline [string trimleft $comment]
383 set i [string first "\n" $headline]
385 set headline [string range $headline 0 $i]
387 set headline [string trimright $headline]
388 set i [string first "\r" $headline]
390 set headline [string trimright [string range $headline 0 $i]]
393 # git rev-list indents the comment by 4 spaces;
394 # if we got this via git cat-file, add the indentation
396 foreach line [split $comment "\n"] {
397 append newcomment " "
398 append newcomment $line
399 append newcomment "\n"
401 set comment $newcomment
403 if {$comdate != {}} {
404 set cdate($id) $comdate
406 set commitinfo($id) [list $headline $auname $audate \
407 $comname $comdate $comment]
410 proc getcommit {id} {
411 global commitdata commitinfo
413 if {[info exists commitdata($id)]} {
414 parsecommit $id $commitdata($id) 1
417 if {![info exists commitinfo($id)]} {
418 set commitinfo($id) {"No commit information available"}
425 global tagids idtags headids idheads tagobjid
426 global otherrefids idotherrefs mainhead mainheadid
428 foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
431 set refd [open [list | git show-ref -d] r]
432 while {[gets $refd line] >= 0} {
433 if {[string index $line 40] ne " "} continue
434 set id [string range $line 0 39]
435 set ref [string range $line 41 end]
436 if {![string match "refs/*" $ref]} continue
437 set name [string range $ref 5 end]
438 if {[string match "remotes/*" $name]} {
439 if {![string match "*/HEAD" $name]} {
440 set headids($name) $id
441 lappend idheads($id) $name
443 } elseif {[string match "heads/*" $name]} {
444 set name [string range $name 6 end]
445 set headids($name) $id
446 lappend idheads($id) $name
447 } elseif {[string match "tags/*" $name]} {
448 # this lets refs/tags/foo^{} overwrite refs/tags/foo,
449 # which is what we want since the former is the commit ID
450 set name [string range $name 5 end]
451 if {[string match "*^{}" $name]} {
452 set name [string range $name 0 end-3]
454 set tagobjid($name) $id
456 set tagids($name) $id
457 lappend idtags($id) $name
459 set otherrefids($name) $id
460 lappend idotherrefs($id) $name
467 set thehead [exec git symbolic-ref HEAD]
468 if {[string match "refs/heads/*" $thehead]} {
469 set mainhead [string range $thehead 11 end]
470 if {[info exists headids($mainhead)]} {
471 set mainheadid $headids($mainhead)
477 # skip over fake commits
478 proc first_real_row {} {
479 global nullid nullid2 displayorder numcommits
481 for {set row 0} {$row < $numcommits} {incr row} {
482 set id [lindex $displayorder $row]
483 if {$id ne $nullid && $id ne $nullid2} {
490 # update things for a head moved to a child of its previous location
491 proc movehead {id name} {
492 global headids idheads
494 removehead $headids($name) $name
495 set headids($name) $id
496 lappend idheads($id) $name
499 # update things when a head has been removed
500 proc removehead {id name} {
501 global headids idheads
503 if {$idheads($id) eq $name} {
506 set i [lsearch -exact $idheads($id) $name]
508 set idheads($id) [lreplace $idheads($id) $i $i]
514 proc show_error {w top msg} {
515 message $w.m -text $msg -justify center -aspect 400
516 pack $w.m -side top -fill x -padx 20 -pady 20
517 button $w.ok -text OK -command "destroy $top"
518 pack $w.ok -side bottom -fill x
519 bind $top <Visibility> "grab $top; focus $top"
520 bind $top <Key-Return> "destroy $top"
524 proc error_popup msg {
528 show_error $w $w $msg
531 proc confirm_popup msg {
537 message $w.m -text $msg -justify center -aspect 400
538 pack $w.m -side top -fill x -padx 20 -pady 20
539 button $w.ok -text OK -command "set confirm_ok 1; destroy $w"
540 pack $w.ok -side left -fill x
541 button $w.cancel -text Cancel -command "destroy $w"
542 pack $w.cancel -side right -fill x
543 bind $w <Visibility> "grab $w; focus $w"
549 global canv canv2 canv3 linespc charspc ctext cflist
550 global textfont mainfont uifont tabstop
551 global findtype findtypemenu findloc findstring fstring geometry
552 global entries sha1entry sha1string sha1but
553 global maincursor textcursor curtextcursor
554 global rowctxmenu fakerowmenu mergemax wrapcomment
555 global highlight_files gdttype
556 global searchstring sstring
557 global bgcolor fgcolor bglist fglist diffcolors selectbgcolor
561 .bar add cascade -label "File" -menu .bar.file
562 .bar configure -font $uifont
564 .bar.file add command -label "Update" -command updatecommits
565 .bar.file add command -label "Reread references" -command rereadrefs
566 .bar.file add command -label "Quit" -command doquit
567 .bar.file configure -font $uifont
569 .bar add cascade -label "Edit" -menu .bar.edit
570 .bar.edit add command -label "Preferences" -command doprefs
571 .bar.edit configure -font $uifont
573 menu .bar.view -font $uifont
574 .bar add cascade -label "View" -menu .bar.view
575 .bar.view add command -label "New view..." -command {newview 0}
576 .bar.view add command -label "Edit view..." -command editview \
578 .bar.view add command -label "Delete view" -command delview -state disabled
579 .bar.view add separator
580 .bar.view add radiobutton -label "All files" -command {showview 0} \
581 -variable selectedview -value 0
584 .bar add cascade -label "Help" -menu .bar.help
585 .bar.help add command -label "About gitk" -command about
586 .bar.help add command -label "Key bindings" -command keys
587 .bar.help configure -font $uifont
588 . configure -menu .bar
590 # the gui has upper and lower half, parts of a paned window.
591 panedwindow .ctop -orient vertical
593 # possibly use assumed geometry
594 if {![info exists geometry(pwsash0)]} {
595 set geometry(topheight) [expr {15 * $linespc}]
596 set geometry(topwidth) [expr {80 * $charspc}]
597 set geometry(botheight) [expr {15 * $linespc}]
598 set geometry(botwidth) [expr {50 * $charspc}]
599 set geometry(pwsash0) "[expr {40 * $charspc}] 2"
600 set geometry(pwsash1) "[expr {60 * $charspc}] 2"
603 # the upper half will have a paned window, a scroll bar to the right, and some stuff below
604 frame .tf -height $geometry(topheight) -width $geometry(topwidth)
606 panedwindow .tf.histframe.pwclist -orient horizontal -sashpad 0 -handlesize 4
608 # create three canvases
609 set cscroll .tf.histframe.csb
610 set canv .tf.histframe.pwclist.canv
612 -selectbackground $selectbgcolor \
613 -background $bgcolor -bd 0 \
614 -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll"
615 .tf.histframe.pwclist add $canv
616 set canv2 .tf.histframe.pwclist.canv2
618 -selectbackground $selectbgcolor \
619 -background $bgcolor -bd 0 -yscrollincr $linespc
620 .tf.histframe.pwclist add $canv2
621 set canv3 .tf.histframe.pwclist.canv3
623 -selectbackground $selectbgcolor \
624 -background $bgcolor -bd 0 -yscrollincr $linespc
625 .tf.histframe.pwclist add $canv3
626 eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0)
627 eval .tf.histframe.pwclist sash place 1 $geometry(pwsash1)
629 # a scroll bar to rule them
630 scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
631 pack $cscroll -side right -fill y
632 bind .tf.histframe.pwclist <Configure> {resizeclistpanes %W %w}
633 lappend bglist $canv $canv2 $canv3
634 pack .tf.histframe.pwclist -fill both -expand 1 -side left
636 # we have two button bars at bottom of top frame. Bar 1
638 frame .tf.lbar -height 15
640 set sha1entry .tf.bar.sha1
641 set entries $sha1entry
642 set sha1but .tf.bar.sha1label
643 button $sha1but -text "SHA1 ID: " -state disabled -relief flat \
644 -command gotocommit -width 8 -font $uifont
645 $sha1but conf -disabledforeground [$sha1but cget -foreground]
646 pack .tf.bar.sha1label -side left
647 entry $sha1entry -width 40 -font $textfont -textvariable sha1string
648 trace add variable sha1string write sha1change
649 pack $sha1entry -side left -pady 2
651 image create bitmap bm-left -data {
652 #define left_width 16
653 #define left_height 16
654 static unsigned char left_bits[] = {
655 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
656 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
657 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
659 image create bitmap bm-right -data {
660 #define right_width 16
661 #define right_height 16
662 static unsigned char right_bits[] = {
663 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
664 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
665 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
667 button .tf.bar.leftbut -image bm-left -command goback \
668 -state disabled -width 26
669 pack .tf.bar.leftbut -side left -fill y
670 button .tf.bar.rightbut -image bm-right -command goforw \
671 -state disabled -width 26
672 pack .tf.bar.rightbut -side left -fill y
674 button .tf.bar.findbut -text "Find" -command dofind -font $uifont
675 pack .tf.bar.findbut -side left
677 set fstring .tf.bar.findstring
678 lappend entries $fstring
679 entry $fstring -width 30 -font $textfont -textvariable findstring
680 trace add variable findstring write find_change
681 pack $fstring -side left -expand 1 -fill x -in .tf.bar
683 set findtypemenu [tk_optionMenu .tf.bar.findtype \
684 findtype Exact IgnCase Regexp]
685 trace add variable findtype write find_change
686 .tf.bar.findtype configure -font $uifont
687 .tf.bar.findtype.menu configure -font $uifont
688 set findloc "All fields"
689 tk_optionMenu .tf.bar.findloc findloc "All fields" Headline \
690 Comments Author Committer
691 trace add variable findloc write find_change
692 .tf.bar.findloc configure -font $uifont
693 .tf.bar.findloc.menu configure -font $uifont
694 pack .tf.bar.findloc -side right
695 pack .tf.bar.findtype -side right
697 # build up the bottom bar of upper window
698 label .tf.lbar.flabel -text "Highlight: Commits " \
700 pack .tf.lbar.flabel -side left -fill y
701 set gdttype "touching paths:"
702 set gm [tk_optionMenu .tf.lbar.gdttype gdttype "touching paths:" \
703 "adding/removing string:"]
704 trace add variable gdttype write hfiles_change
705 $gm conf -font $uifont
706 .tf.lbar.gdttype conf -font $uifont
707 pack .tf.lbar.gdttype -side left -fill y
708 entry .tf.lbar.fent -width 25 -font $textfont \
709 -textvariable highlight_files
710 trace add variable highlight_files write hfiles_change
711 lappend entries .tf.lbar.fent
712 pack .tf.lbar.fent -side left -fill x -expand 1
713 label .tf.lbar.vlabel -text " OR in view" -font $uifont
714 pack .tf.lbar.vlabel -side left -fill y
715 global viewhlmenu selectedhlview
716 set viewhlmenu [tk_optionMenu .tf.lbar.vhl selectedhlview None]
717 $viewhlmenu entryconf None -command delvhighlight
718 $viewhlmenu conf -font $uifont
719 .tf.lbar.vhl conf -font $uifont
720 pack .tf.lbar.vhl -side left -fill y
721 label .tf.lbar.rlabel -text " OR " -font $uifont
722 pack .tf.lbar.rlabel -side left -fill y
723 global highlight_related
724 set m [tk_optionMenu .tf.lbar.relm highlight_related None \
725 "Descendent" "Not descendent" "Ancestor" "Not ancestor"]
726 $m conf -font $uifont
727 .tf.lbar.relm conf -font $uifont
728 trace add variable highlight_related write vrel_change
729 pack .tf.lbar.relm -side left -fill y
731 # Finish putting the upper half of the viewer together
732 pack .tf.lbar -in .tf -side bottom -fill x
733 pack .tf.bar -in .tf -side bottom -fill x
734 pack .tf.histframe -fill both -side top -expand 1
736 .ctop paneconfigure .tf -height $geometry(topheight)
737 .ctop paneconfigure .tf -width $geometry(topwidth)
739 # now build up the bottom
740 panedwindow .pwbottom -orient horizontal
742 # lower left, a text box over search bar, scroll bar to the right
743 # if we know window height, then that will set the lower text height, otherwise
744 # we set lower text height which will drive window height
745 if {[info exists geometry(main)]} {
746 frame .bleft -width $geometry(botwidth)
748 frame .bleft -width $geometry(botwidth) -height $geometry(botheight)
753 button .bleft.top.search -text "Search" -command dosearch \
755 pack .bleft.top.search -side left -padx 5
756 set sstring .bleft.top.sstring
757 entry $sstring -width 20 -font $textfont -textvariable searchstring
758 lappend entries $sstring
759 trace add variable searchstring write incrsearch
760 pack $sstring -side left -expand 1 -fill x
761 radiobutton .bleft.mid.diff -text "Diff" \
762 -command changediffdisp -variable diffelide -value {0 0}
763 radiobutton .bleft.mid.old -text "Old version" \
764 -command changediffdisp -variable diffelide -value {0 1}
765 radiobutton .bleft.mid.new -text "New version" \
766 -command changediffdisp -variable diffelide -value {1 0}
767 pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left
768 set ctext .bleft.ctext
769 text $ctext -background $bgcolor -foreground $fgcolor \
770 -tabs "[expr {$tabstop * $charspc}]" \
771 -state disabled -font $textfont \
772 -yscrollcommand scrolltext -wrap none
773 scrollbar .bleft.sb -command "$ctext yview"
774 pack .bleft.top -side top -fill x
775 pack .bleft.mid -side top -fill x
776 pack .bleft.sb -side right -fill y
777 pack $ctext -side left -fill both -expand 1
778 lappend bglist $ctext
779 lappend fglist $ctext
781 $ctext tag conf comment -wrap $wrapcomment
782 $ctext tag conf filesep -font [concat $textfont bold] -back "#aaaaaa"
783 $ctext tag conf hunksep -fore [lindex $diffcolors 2]
784 $ctext tag conf d0 -fore [lindex $diffcolors 0]
785 $ctext tag conf d1 -fore [lindex $diffcolors 1]
786 $ctext tag conf m0 -fore red
787 $ctext tag conf m1 -fore blue
788 $ctext tag conf m2 -fore green
789 $ctext tag conf m3 -fore purple
790 $ctext tag conf m4 -fore brown
791 $ctext tag conf m5 -fore "#009090"
792 $ctext tag conf m6 -fore magenta
793 $ctext tag conf m7 -fore "#808000"
794 $ctext tag conf m8 -fore "#009000"
795 $ctext tag conf m9 -fore "#ff0080"
796 $ctext tag conf m10 -fore cyan
797 $ctext tag conf m11 -fore "#b07070"
798 $ctext tag conf m12 -fore "#70b0f0"
799 $ctext tag conf m13 -fore "#70f0b0"
800 $ctext tag conf m14 -fore "#f0b070"
801 $ctext tag conf m15 -fore "#ff70b0"
802 $ctext tag conf mmax -fore darkgrey
804 $ctext tag conf mresult -font [concat $textfont bold]
805 $ctext tag conf msep -font [concat $textfont bold]
806 $ctext tag conf found -back yellow
809 .pwbottom paneconfigure .bleft -width $geometry(botwidth)
814 radiobutton .bright.mode.patch -text "Patch" \
815 -command reselectline -variable cmitmode -value "patch"
816 .bright.mode.patch configure -font $uifont
817 radiobutton .bright.mode.tree -text "Tree" \
818 -command reselectline -variable cmitmode -value "tree"
819 .bright.mode.tree configure -font $uifont
820 grid .bright.mode.patch .bright.mode.tree -sticky ew
821 pack .bright.mode -side top -fill x
822 set cflist .bright.cfiles
823 set indent [font measure $mainfont "nn"]
825 -selectbackground $selectbgcolor \
826 -background $bgcolor -foreground $fgcolor \
828 -tabs [list $indent [expr {2 * $indent}]] \
829 -yscrollcommand ".bright.sb set" \
830 -cursor [. cget -cursor] \
831 -spacing1 1 -spacing3 1
832 lappend bglist $cflist
833 lappend fglist $cflist
834 scrollbar .bright.sb -command "$cflist yview"
835 pack .bright.sb -side right -fill y
836 pack $cflist -side left -fill both -expand 1
837 $cflist tag configure highlight \
838 -background [$cflist cget -selectbackground]
839 $cflist tag configure bold -font [concat $mainfont bold]
841 .pwbottom add .bright
844 # restore window position if known
845 if {[info exists geometry(main)]} {
846 wm geometry . "$geometry(main)"
849 if {[tk windowingsystem] eq {aqua}} {
855 bind .pwbottom <Configure> {resizecdetpanes %W %w}
856 pack .ctop -fill both -expand 1
857 bindall <1> {selcanvline %W %x %y}
858 #bindall <B1-Motion> {selcanvline %W %x %y}
859 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
860 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
861 bindall <2> "canvscan mark %W %x %y"
862 bindall <B2-Motion> "canvscan dragto %W %x %y"
863 bindkey <Home> selfirstline
864 bindkey <End> sellastline
865 bind . <Key-Up> "selnextline -1"
866 bind . <Key-Down> "selnextline 1"
867 bind . <Shift-Key-Up> "next_highlight -1"
868 bind . <Shift-Key-Down> "next_highlight 1"
869 bindkey <Key-Right> "goforw"
870 bindkey <Key-Left> "goback"
871 bind . <Key-Prior> "selnextpage -1"
872 bind . <Key-Next> "selnextpage 1"
873 bind . <$M1B-Home> "allcanvs yview moveto 0.0"
874 bind . <$M1B-End> "allcanvs yview moveto 1.0"
875 bind . <$M1B-Key-Up> "allcanvs yview scroll -1 units"
876 bind . <$M1B-Key-Down> "allcanvs yview scroll 1 units"
877 bind . <$M1B-Key-Prior> "allcanvs yview scroll -1 pages"
878 bind . <$M1B-Key-Next> "allcanvs yview scroll 1 pages"
879 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
880 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
881 bindkey <Key-space> "$ctext yview scroll 1 pages"
882 bindkey p "selnextline -1"
883 bindkey n "selnextline 1"
886 bindkey i "selnextline -1"
887 bindkey k "selnextline 1"
890 bindkey b "$ctext yview scroll -1 pages"
891 bindkey d "$ctext yview scroll 18 units"
892 bindkey u "$ctext yview scroll -18 units"
893 bindkey / {findnext 1}
894 bindkey <Key-Return> {findnext 0}
897 bindkey <F5> updatecommits
898 bind . <$M1B-q> doquit
899 bind . <$M1B-f> dofind
900 bind . <$M1B-g> {findnext 0}
901 bind . <$M1B-r> dosearchback
902 bind . <$M1B-s> dosearch
903 bind . <$M1B-equal> {incrfont 1}
904 bind . <$M1B-KP_Add> {incrfont 1}
905 bind . <$M1B-minus> {incrfont -1}
906 bind . <$M1B-KP_Subtract> {incrfont -1}
907 wm protocol . WM_DELETE_WINDOW doquit
908 bind . <Button-1> "click %W"
909 bind $fstring <Key-Return> dofind
910 bind $sha1entry <Key-Return> gotocommit
911 bind $sha1entry <<PasteSelection>> clearsha1
912 bind $cflist <1> {sel_flist %W %x %y; break}
913 bind $cflist <B1-Motion> {sel_flist %W %x %y; break}
914 bind $cflist <ButtonRelease-1> {treeclick %W %x %y}
915 bind $cflist <Button-3> {pop_flist_menu %W %X %Y %x %y}
917 set maincursor [. cget -cursor]
918 set textcursor [$ctext cget -cursor]
919 set curtextcursor $textcursor
921 set rowctxmenu .rowctxmenu
922 menu $rowctxmenu -tearoff 0
923 $rowctxmenu add command -label "Diff this -> selected" \
924 -command {diffvssel 0}
925 $rowctxmenu add command -label "Diff selected -> this" \
926 -command {diffvssel 1}
927 $rowctxmenu add command -label "Make patch" -command mkpatch
928 $rowctxmenu add command -label "Create tag" -command mktag
929 $rowctxmenu add command -label "Write commit to file" -command writecommit
930 $rowctxmenu add command -label "Create new branch" -command mkbranch
931 $rowctxmenu add command -label "Cherry-pick this commit" \
933 $rowctxmenu add command -label "Reset HEAD branch to here" \
936 set fakerowmenu .fakerowmenu
937 menu $fakerowmenu -tearoff 0
938 $fakerowmenu add command -label "Diff this -> selected" \
939 -command {diffvssel 0}
940 $fakerowmenu add command -label "Diff selected -> this" \
941 -command {diffvssel 1}
942 $fakerowmenu add command -label "Make patch" -command mkpatch
943 # $fakerowmenu add command -label "Commit" -command {mkcommit 0}
944 # $fakerowmenu add command -label "Commit all" -command {mkcommit 1}
945 # $fakerowmenu add command -label "Revert local changes" -command revertlocal
947 set headctxmenu .headctxmenu
948 menu $headctxmenu -tearoff 0
949 $headctxmenu add command -label "Check out this branch" \
951 $headctxmenu add command -label "Remove this branch" \
955 set flist_menu .flistctxmenu
956 menu $flist_menu -tearoff 0
957 $flist_menu add command -label "Highlight this too" \
958 -command {flist_hl 0}
959 $flist_menu add command -label "Highlight this only" \
960 -command {flist_hl 1}
963 # mouse-2 makes all windows scan vertically, but only the one
964 # the cursor is in scans horizontally
965 proc canvscan {op w x y} {
966 global canv canv2 canv3
967 foreach c [list $canv $canv2 $canv3] {
976 proc scrollcanv {cscroll f0 f1} {
982 # when we make a key binding for the toplevel, make sure
983 # it doesn't get triggered when that key is pressed in the
984 # find string entry widget.
985 proc bindkey {ev script} {
988 set escript [bind Entry $ev]
989 if {$escript == {}} {
990 set escript [bind Entry <Key>]
993 bind $e $ev "$escript; break"
997 # set the focus back to the toplevel for any click outside
1001 foreach e $entries {
1002 if {$w == $e} return
1007 proc savestuff {w} {
1008 global canv canv2 canv3 ctext cflist mainfont textfont uifont tabstop
1009 global stuffsaved findmergefiles maxgraphpct
1010 global maxwidth showneartags showlocalchanges
1011 global viewname viewfiles viewargs viewperm nextviewnum
1012 global cmitmode wrapcomment
1013 global colors bgcolor fgcolor diffcolors selectbgcolor
1015 if {$stuffsaved} return
1016 if {![winfo viewable .]} return
1018 set f [open "~/.gitk-new" w]
1019 puts $f [list set mainfont $mainfont]
1020 puts $f [list set textfont $textfont]
1021 puts $f [list set uifont $uifont]
1022 puts $f [list set tabstop $tabstop]
1023 puts $f [list set findmergefiles $findmergefiles]
1024 puts $f [list set maxgraphpct $maxgraphpct]
1025 puts $f [list set maxwidth $maxwidth]
1026 puts $f [list set cmitmode $cmitmode]
1027 puts $f [list set wrapcomment $wrapcomment]
1028 puts $f [list set showneartags $showneartags]
1029 puts $f [list set showlocalchanges $showlocalchanges]
1030 puts $f [list set bgcolor $bgcolor]
1031 puts $f [list set fgcolor $fgcolor]
1032 puts $f [list set colors $colors]
1033 puts $f [list set diffcolors $diffcolors]
1034 puts $f [list set selectbgcolor $selectbgcolor]
1036 puts $f "set geometry(main) [wm geometry .]"
1037 puts $f "set geometry(topwidth) [winfo width .tf]"
1038 puts $f "set geometry(topheight) [winfo height .tf]"
1039 puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\""
1040 puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sash coord 1]\""
1041 puts $f "set geometry(botwidth) [winfo width .bleft]"
1042 puts $f "set geometry(botheight) [winfo height .bleft]"
1044 puts -nonewline $f "set permviews {"
1045 for {set v 0} {$v < $nextviewnum} {incr v} {
1046 if {$viewperm($v)} {
1047 puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v)]}"
1052 file rename -force "~/.gitk-new" "~/.gitk"
1057 proc resizeclistpanes {win w} {
1059 if {[info exists oldwidth($win)]} {
1060 set s0 [$win sash coord 0]
1061 set s1 [$win sash coord 1]
1063 set sash0 [expr {int($w/2 - 2)}]
1064 set sash1 [expr {int($w*5/6 - 2)}]
1066 set factor [expr {1.0 * $w / $oldwidth($win)}]
1067 set sash0 [expr {int($factor * [lindex $s0 0])}]
1068 set sash1 [expr {int($factor * [lindex $s1 0])}]
1072 if {$sash1 < $sash0 + 20} {
1073 set sash1 [expr {$sash0 + 20}]
1075 if {$sash1 > $w - 10} {
1076 set sash1 [expr {$w - 10}]
1077 if {$sash0 > $sash1 - 20} {
1078 set sash0 [expr {$sash1 - 20}]
1082 $win sash place 0 $sash0 [lindex $s0 1]
1083 $win sash place 1 $sash1 [lindex $s1 1]
1085 set oldwidth($win) $w
1088 proc resizecdetpanes {win w} {
1090 if {[info exists oldwidth($win)]} {
1091 set s0 [$win sash coord 0]
1093 set sash0 [expr {int($w*3/4 - 2)}]
1095 set factor [expr {1.0 * $w / $oldwidth($win)}]
1096 set sash0 [expr {int($factor * [lindex $s0 0])}]
1100 if {$sash0 > $w - 15} {
1101 set sash0 [expr {$w - 15}]
1104 $win sash place 0 $sash0 [lindex $s0 1]
1106 set oldwidth($win) $w
1109 proc allcanvs args {
1110 global canv canv2 canv3
1116 proc bindall {event action} {
1117 global canv canv2 canv3
1118 bind $canv $event $action
1119 bind $canv2 $event $action
1120 bind $canv3 $event $action
1126 if {[winfo exists $w]} {
1131 wm title $w "About gitk"
1132 message $w.m -text {
1133 Gitk - a commit viewer for git
1135 Copyright © 2005-2006 Paul Mackerras
1137 Use and redistribute under the terms of the GNU General Public License} \
1138 -justify center -aspect 400 -border 2 -bg white -relief groove
1139 pack $w.m -side top -fill x -padx 2 -pady 2
1140 $w.m configure -font $uifont
1141 button $w.ok -text Close -command "destroy $w" -default active
1142 pack $w.ok -side bottom
1143 $w.ok configure -font $uifont
1144 bind $w <Visibility> "focus $w.ok"
1145 bind $w <Key-Escape> "destroy $w"
1146 bind $w <Key-Return> "destroy $w"
1152 if {[winfo exists $w]} {
1156 if {[tk windowingsystem] eq {aqua}} {
1162 wm title $w "Gitk key bindings"
1163 message $w.m -text "
1167 <Home> Move to first commit
1168 <End> Move to last commit
1169 <Up>, p, i Move up one commit
1170 <Down>, n, k Move down one commit
1171 <Left>, z, j Go back in history list
1172 <Right>, x, l Go forward in history list
1173 <PageUp> Move up one page in commit list
1174 <PageDown> Move down one page in commit list
1175 <$M1T-Home> Scroll to top of commit list
1176 <$M1T-End> Scroll to bottom of commit list
1177 <$M1T-Up> Scroll commit list up one line
1178 <$M1T-Down> Scroll commit list down one line
1179 <$M1T-PageUp> Scroll commit list up one page
1180 <$M1T-PageDown> Scroll commit list down one page
1181 <Shift-Up> Move to previous highlighted line
1182 <Shift-Down> Move to next highlighted line
1183 <Delete>, b Scroll diff view up one page
1184 <Backspace> Scroll diff view up one page
1185 <Space> Scroll diff view down one page
1186 u Scroll diff view up 18 lines
1187 d Scroll diff view down 18 lines
1189 <$M1T-G> Move to next find hit
1190 <Return> Move to next find hit
1191 / Move to next find hit, or redo find
1192 ? Move to previous find hit
1193 f Scroll diff view to next file
1194 <$M1T-S> Search for next hit in diff view
1195 <$M1T-R> Search for previous hit in diff view
1196 <$M1T-KP+> Increase font size
1197 <$M1T-plus> Increase font size
1198 <$M1T-KP-> Decrease font size
1199 <$M1T-minus> Decrease font size
1202 -justify left -bg white -border 2 -relief groove
1203 pack $w.m -side top -fill both -padx 2 -pady 2
1204 $w.m configure -font $uifont
1205 button $w.ok -text Close -command "destroy $w" -default active
1206 pack $w.ok -side bottom
1207 $w.ok configure -font $uifont
1208 bind $w <Visibility> "focus $w.ok"
1209 bind $w <Key-Escape> "destroy $w"
1210 bind $w <Key-Return> "destroy $w"
1213 # Procedures for manipulating the file list window at the
1214 # bottom right of the overall window.
1216 proc treeview {w l openlevs} {
1217 global treecontents treediropen treeheight treeparent treeindex
1227 set treecontents() {}
1228 $w conf -state normal
1230 while {[string range $f 0 $prefixend] ne $prefix} {
1231 if {$lev <= $openlevs} {
1232 $w mark set e:$treeindex($prefix) "end -1c"
1233 $w mark gravity e:$treeindex($prefix) left
1235 set treeheight($prefix) $ht
1236 incr ht [lindex $htstack end]
1237 set htstack [lreplace $htstack end end]
1238 set prefixend [lindex $prefendstack end]
1239 set prefendstack [lreplace $prefendstack end end]
1240 set prefix [string range $prefix 0 $prefixend]
1243 set tail [string range $f [expr {$prefixend+1}] end]
1244 while {[set slash [string first "/" $tail]] >= 0} {
1247 lappend prefendstack $prefixend
1248 incr prefixend [expr {$slash + 1}]
1249 set d [string range $tail 0 $slash]
1250 lappend treecontents($prefix) $d
1251 set oldprefix $prefix
1253 set treecontents($prefix) {}
1254 set treeindex($prefix) [incr ix]
1255 set treeparent($prefix) $oldprefix
1256 set tail [string range $tail [expr {$slash+1}] end]
1257 if {$lev <= $openlevs} {
1259 set treediropen($prefix) [expr {$lev < $openlevs}]
1260 set bm [expr {$lev == $openlevs? "tri-rt": "tri-dn"}]
1261 $w mark set d:$ix "end -1c"
1262 $w mark gravity d:$ix left
1264 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
1266 $w image create end -align center -image $bm -padx 1 \
1268 $w insert end $d [highlight_tag $prefix]
1269 $w mark set s:$ix "end -1c"
1270 $w mark gravity s:$ix left
1275 if {$lev <= $openlevs} {
1278 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
1280 $w insert end $tail [highlight_tag $f]
1282 lappend treecontents($prefix) $tail
1285 while {$htstack ne {}} {
1286 set treeheight($prefix) $ht
1287 incr ht [lindex $htstack end]
1288 set htstack [lreplace $htstack end end]
1289 set prefixend [lindex $prefendstack end]
1290 set prefendstack [lreplace $prefendstack end end]
1291 set prefix [string range $prefix 0 $prefixend]
1293 $w conf -state disabled
1296 proc linetoelt {l} {
1297 global treeheight treecontents
1302 foreach e $treecontents($prefix) {
1307 if {[string index $e end] eq "/"} {
1308 set n $treeheight($prefix$e)
1320 proc highlight_tree {y prefix} {
1321 global treeheight treecontents cflist
1323 foreach e $treecontents($prefix) {
1325 if {[highlight_tag $path] ne {}} {
1326 $cflist tag add bold $y.0 "$y.0 lineend"
1329 if {[string index $e end] eq "/" && $treeheight($path) > 1} {
1330 set y [highlight_tree $y $path]
1336 proc treeclosedir {w dir} {
1337 global treediropen treeheight treeparent treeindex
1339 set ix $treeindex($dir)
1340 $w conf -state normal
1341 $w delete s:$ix e:$ix
1342 set treediropen($dir) 0
1343 $w image configure a:$ix -image tri-rt
1344 $w conf -state disabled
1345 set n [expr {1 - $treeheight($dir)}]
1346 while {$dir ne {}} {
1347 incr treeheight($dir) $n
1348 set dir $treeparent($dir)
1352 proc treeopendir {w dir} {
1353 global treediropen treeheight treeparent treecontents treeindex
1355 set ix $treeindex($dir)
1356 $w conf -state normal
1357 $w image configure a:$ix -image tri-dn
1358 $w mark set e:$ix s:$ix
1359 $w mark gravity e:$ix right
1362 set n [llength $treecontents($dir)]
1363 for {set x $dir} {$x ne {}} {set x $treeparent($x)} {
1366 incr treeheight($x) $n
1368 foreach e $treecontents($dir) {
1370 if {[string index $e end] eq "/"} {
1371 set iy $treeindex($de)
1372 $w mark set d:$iy e:$ix
1373 $w mark gravity d:$iy left
1374 $w insert e:$ix $str
1375 set treediropen($de) 0
1376 $w image create e:$ix -align center -image tri-rt -padx 1 \
1378 $w insert e:$ix $e [highlight_tag $de]
1379 $w mark set s:$iy e:$ix
1380 $w mark gravity s:$iy left
1381 set treeheight($de) 1
1383 $w insert e:$ix $str
1384 $w insert e:$ix $e [highlight_tag $de]
1387 $w mark gravity e:$ix left
1388 $w conf -state disabled
1389 set treediropen($dir) 1
1390 set top [lindex [split [$w index @0,0] .] 0]
1391 set ht [$w cget -height]
1392 set l [lindex [split [$w index s:$ix] .] 0]
1395 } elseif {$l + $n + 1 > $top + $ht} {
1396 set top [expr {$l + $n + 2 - $ht}]
1404 proc treeclick {w x y} {
1405 global treediropen cmitmode ctext cflist cflist_top
1407 if {$cmitmode ne "tree"} return
1408 if {![info exists cflist_top]} return
1409 set l [lindex [split [$w index "@$x,$y"] "."] 0]
1410 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
1411 $cflist tag add highlight $l.0 "$l.0 lineend"
1417 set e [linetoelt $l]
1418 if {[string index $e end] ne "/"} {
1420 } elseif {$treediropen($e)} {
1427 proc setfilelist {id} {
1428 global treefilelist cflist
1430 treeview $cflist $treefilelist($id) 0
1433 image create bitmap tri-rt -background black -foreground blue -data {
1434 #define tri-rt_width 13
1435 #define tri-rt_height 13
1436 static unsigned char tri-rt_bits[] = {
1437 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x70, 0x00, 0xf0, 0x00,
1438 0xf0, 0x01, 0xf0, 0x00, 0x70, 0x00, 0x30, 0x00, 0x10, 0x00, 0x00, 0x00,
1441 #define tri-rt-mask_width 13
1442 #define tri-rt-mask_height 13
1443 static unsigned char tri-rt-mask_bits[] = {
1444 0x08, 0x00, 0x18, 0x00, 0x38, 0x00, 0x78, 0x00, 0xf8, 0x00, 0xf8, 0x01,
1445 0xf8, 0x03, 0xf8, 0x01, 0xf8, 0x00, 0x78, 0x00, 0x38, 0x00, 0x18, 0x00,
1448 image create bitmap tri-dn -background black -foreground blue -data {
1449 #define tri-dn_width 13
1450 #define tri-dn_height 13
1451 static unsigned char tri-dn_bits[] = {
1452 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0xf8, 0x03,
1453 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1456 #define tri-dn-mask_width 13
1457 #define tri-dn-mask_height 13
1458 static unsigned char tri-dn-mask_bits[] = {
1459 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1f, 0xfe, 0x0f, 0xfc, 0x07,
1460 0xf8, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
1464 proc init_flist {first} {
1465 global cflist cflist_top selectedline difffilestart
1467 $cflist conf -state normal
1468 $cflist delete 0.0 end
1470 $cflist insert end $first
1472 $cflist tag add highlight 1.0 "1.0 lineend"
1474 catch {unset cflist_top}
1476 $cflist conf -state disabled
1477 set difffilestart {}
1480 proc highlight_tag {f} {
1481 global highlight_paths
1483 foreach p $highlight_paths {
1484 if {[string match $p $f]} {
1491 proc highlight_filelist {} {
1492 global cmitmode cflist
1494 $cflist conf -state normal
1495 if {$cmitmode ne "tree"} {
1496 set end [lindex [split [$cflist index end] .] 0]
1497 for {set l 2} {$l < $end} {incr l} {
1498 set line [$cflist get $l.0 "$l.0 lineend"]
1499 if {[highlight_tag $line] ne {}} {
1500 $cflist tag add bold $l.0 "$l.0 lineend"
1506 $cflist conf -state disabled
1509 proc unhighlight_filelist {} {
1512 $cflist conf -state normal
1513 $cflist tag remove bold 1.0 end
1514 $cflist conf -state disabled
1517 proc add_flist {fl} {
1520 $cflist conf -state normal
1522 $cflist insert end "\n"
1523 $cflist insert end $f [highlight_tag $f]
1525 $cflist conf -state disabled
1528 proc sel_flist {w x y} {
1529 global ctext difffilestart cflist cflist_top cmitmode
1531 if {$cmitmode eq "tree"} return
1532 if {![info exists cflist_top]} return
1533 set l [lindex [split [$w index "@$x,$y"] "."] 0]
1534 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
1535 $cflist tag add highlight $l.0 "$l.0 lineend"
1540 catch {$ctext yview [lindex $difffilestart [expr {$l - 2}]]}
1544 proc pop_flist_menu {w X Y x y} {
1545 global ctext cflist cmitmode flist_menu flist_menu_file
1546 global treediffs diffids
1548 set l [lindex [split [$w index "@$x,$y"] "."] 0]
1550 if {$cmitmode eq "tree"} {
1551 set e [linetoelt $l]
1552 if {[string index $e end] eq "/"} return
1554 set e [lindex $treediffs($diffids) [expr {$l-2}]]
1556 set flist_menu_file $e
1557 tk_popup $flist_menu $X $Y
1560 proc flist_hl {only} {
1561 global flist_menu_file highlight_files
1563 set x [shellquote $flist_menu_file]
1564 if {$only || $highlight_files eq {}} {
1565 set highlight_files $x
1567 append highlight_files " " $x
1571 # Functions for adding and removing shell-type quoting
1573 proc shellquote {str} {
1574 if {![string match "*\['\"\\ \t]*" $str]} {
1577 if {![string match "*\['\"\\]*" $str]} {
1580 if {![string match "*'*" $str]} {
1583 return "\"[string map {\" \\\" \\ \\\\} $str]\""
1586 proc shellarglist {l} {
1592 append str [shellquote $a]
1597 proc shelldequote {str} {
1602 if {![regexp -start $used -indices "\['\"\\\\ \t]" $str first]} {
1603 append ret [string range $str $used end]
1604 set used [string length $str]
1607 set first [lindex $first 0]
1608 set ch [string index $str $first]
1609 if {$first > $used} {
1610 append ret [string range $str $used [expr {$first - 1}]]
1613 if {$ch eq " " || $ch eq "\t"} break
1616 set first [string first "'" $str $used]
1618 error "unmatched single-quote"
1620 append ret [string range $str $used [expr {$first - 1}]]
1625 if {$used >= [string length $str]} {
1626 error "trailing backslash"
1628 append ret [string index $str $used]
1633 if {![regexp -start $used -indices "\[\"\\\\]" $str first]} {
1634 error "unmatched double-quote"
1636 set first [lindex $first 0]
1637 set ch [string index $str $first]
1638 if {$first > $used} {
1639 append ret [string range $str $used [expr {$first - 1}]]
1642 if {$ch eq "\""} break
1644 append ret [string index $str $used]
1648 return [list $used $ret]
1651 proc shellsplit {str} {
1654 set str [string trimleft $str]
1655 if {$str eq {}} break
1656 set dq [shelldequote $str]
1657 set n [lindex $dq 0]
1658 set word [lindex $dq 1]
1659 set str [string range $str $n end]
1665 # Code to implement multiple views
1667 proc newview {ishighlight} {
1668 global nextviewnum newviewname newviewperm uifont newishighlight
1669 global newviewargs revtreeargs
1671 set newishighlight $ishighlight
1673 if {[winfo exists $top]} {
1677 set newviewname($nextviewnum) "View $nextviewnum"
1678 set newviewperm($nextviewnum) 0
1679 set newviewargs($nextviewnum) [shellarglist $revtreeargs]
1680 vieweditor $top $nextviewnum "Gitk view definition"
1685 global viewname viewperm newviewname newviewperm
1686 global viewargs newviewargs
1688 set top .gitkvedit-$curview
1689 if {[winfo exists $top]} {
1693 set newviewname($curview) $viewname($curview)
1694 set newviewperm($curview) $viewperm($curview)
1695 set newviewargs($curview) [shellarglist $viewargs($curview)]
1696 vieweditor $top $curview "Gitk: edit view $viewname($curview)"
1699 proc vieweditor {top n title} {
1700 global newviewname newviewperm viewfiles
1704 wm title $top $title
1705 label $top.nl -text "Name" -font $uifont
1706 entry $top.name -width 20 -textvariable newviewname($n) -font $uifont
1707 grid $top.nl $top.name -sticky w -pady 5
1708 checkbutton $top.perm -text "Remember this view" -variable newviewperm($n) \
1710 grid $top.perm - -pady 5 -sticky w
1711 message $top.al -aspect 1000 -font $uifont \
1712 -text "Commits to include (arguments to git rev-list):"
1713 grid $top.al - -sticky w -pady 5
1714 entry $top.args -width 50 -textvariable newviewargs($n) \
1715 -background white -font $uifont
1716 grid $top.args - -sticky ew -padx 5
1717 message $top.l -aspect 1000 -font $uifont \
1718 -text "Enter files and directories to include, one per line:"
1719 grid $top.l - -sticky w
1720 text $top.t -width 40 -height 10 -background white -font $uifont
1721 if {[info exists viewfiles($n)]} {
1722 foreach f $viewfiles($n) {
1723 $top.t insert end $f
1724 $top.t insert end "\n"
1726 $top.t delete {end - 1c} end
1727 $top.t mark set insert 0.0
1729 grid $top.t - -sticky ew -padx 5
1731 button $top.buts.ok -text "OK" -command [list newviewok $top $n] \
1733 button $top.buts.can -text "Cancel" -command [list destroy $top] \
1735 grid $top.buts.ok $top.buts.can
1736 grid columnconfigure $top.buts 0 -weight 1 -uniform a
1737 grid columnconfigure $top.buts 1 -weight 1 -uniform a
1738 grid $top.buts - -pady 10 -sticky ew
1742 proc doviewmenu {m first cmd op argv} {
1743 set nmenu [$m index end]
1744 for {set i $first} {$i <= $nmenu} {incr i} {
1745 if {[$m entrycget $i -command] eq $cmd} {
1746 eval $m $op $i $argv
1752 proc allviewmenus {n op args} {
1755 doviewmenu .bar.view 5 [list showview $n] $op $args
1756 doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
1759 proc newviewok {top n} {
1760 global nextviewnum newviewperm newviewname newishighlight
1761 global viewname viewfiles viewperm selectedview curview
1762 global viewargs newviewargs viewhlmenu
1765 set newargs [shellsplit $newviewargs($n)]
1767 error_popup "Error in commit selection arguments: $err"
1773 foreach f [split [$top.t get 0.0 end] "\n"] {
1774 set ft [string trim $f]
1779 if {![info exists viewfiles($n)]} {
1780 # creating a new view
1782 set viewname($n) $newviewname($n)
1783 set viewperm($n) $newviewperm($n)
1784 set viewfiles($n) $files
1785 set viewargs($n) $newargs
1787 if {!$newishighlight} {
1790 run addvhighlight $n
1793 # editing an existing view
1794 set viewperm($n) $newviewperm($n)
1795 if {$newviewname($n) ne $viewname($n)} {
1796 set viewname($n) $newviewname($n)
1797 doviewmenu .bar.view 5 [list showview $n] \
1798 entryconf [list -label $viewname($n)]
1799 doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
1800 entryconf [list -label $viewname($n) -value $viewname($n)]
1802 if {$files ne $viewfiles($n) || $newargs ne $viewargs($n)} {
1803 set viewfiles($n) $files
1804 set viewargs($n) $newargs
1805 if {$curview == $n} {
1810 catch {destroy $top}
1814 global curview viewdata viewperm hlview selectedhlview
1816 if {$curview == 0} return
1817 if {[info exists hlview] && $hlview == $curview} {
1818 set selectedhlview None
1821 allviewmenus $curview delete
1822 set viewdata($curview) {}
1823 set viewperm($curview) 0
1827 proc addviewmenu {n} {
1828 global viewname viewhlmenu
1830 .bar.view add radiobutton -label $viewname($n) \
1831 -command [list showview $n] -variable selectedview -value $n
1832 $viewhlmenu add radiobutton -label $viewname($n) \
1833 -command [list addvhighlight $n] -variable selectedhlview
1836 proc flatten {var} {
1840 foreach i [array names $var] {
1841 lappend ret $i [set $var\($i\)]
1846 proc unflatten {var l} {
1856 global curview viewdata viewfiles
1857 global displayorder parentlist rowidlist
1858 global colormap rowtextx commitrow nextcolor canvxmax
1859 global numcommits rowrangelist commitlisted idrowranges rowchk
1860 global selectedline currentid canv canvy0
1862 global pending_select phase
1863 global commitidx rowlaidout rowoptim
1865 global selectedview selectfirst
1866 global vparentlist vdisporder vcmitlisted
1867 global hlview selectedhlview
1869 if {$n == $curview} return
1871 if {[info exists selectedline]} {
1872 set selid $currentid
1873 set y [yc $selectedline]
1874 set ymax [lindex [$canv cget -scrollregion] 3]
1875 set span [$canv yview]
1876 set ytop [expr {[lindex $span 0] * $ymax}]
1877 set ybot [expr {[lindex $span 1] * $ymax}]
1878 if {$ytop < $y && $y < $ybot} {
1879 set yscreen [expr {$y - $ytop}]
1881 set yscreen [expr {($ybot - $ytop) / 2}]
1883 } elseif {[info exists pending_select]} {
1884 set selid $pending_select
1885 unset pending_select
1889 if {$curview >= 0} {
1890 set vparentlist($curview) $parentlist
1891 set vdisporder($curview) $displayorder
1892 set vcmitlisted($curview) $commitlisted
1894 set viewdata($curview) \
1895 [list $phase $rowidlist {} $rowrangelist \
1896 [flatten idrowranges] [flatten idinlist] \
1897 $rowlaidout $rowoptim $numcommits]
1898 } elseif {![info exists viewdata($curview)]
1899 || [lindex $viewdata($curview) 0] ne {}} {
1900 set viewdata($curview) \
1901 [list {} $rowidlist {} $rowrangelist]
1904 catch {unset treediffs}
1906 if {[info exists hlview] && $hlview == $n} {
1908 set selectedhlview None
1913 .bar.view entryconf Edit* -state [expr {$n == 0? "disabled": "normal"}]
1914 .bar.view entryconf Delete* -state [expr {$n == 0? "disabled": "normal"}]
1916 if {![info exists viewdata($n)]} {
1918 set pending_select $selid
1925 set phase [lindex $v 0]
1926 set displayorder $vdisporder($n)
1927 set parentlist $vparentlist($n)
1928 set commitlisted $vcmitlisted($n)
1929 set rowidlist [lindex $v 1]
1930 set rowrangelist [lindex $v 3]
1932 set numcommits [llength $displayorder]
1933 catch {unset idrowranges}
1935 unflatten idrowranges [lindex $v 4]
1936 unflatten idinlist [lindex $v 5]
1937 set rowlaidout [lindex $v 6]
1938 set rowoptim [lindex $v 7]
1939 set numcommits [lindex $v 8]
1940 catch {unset rowchk}
1943 catch {unset colormap}
1944 catch {unset rowtextx}
1946 set canvxmax [$canv cget -width]
1953 if {$selid ne {} && [info exists commitrow($n,$selid)]} {
1954 set row $commitrow($n,$selid)
1955 # try to get the selected row in the same position on the screen
1956 set ymax [lindex [$canv cget -scrollregion] 3]
1957 set ytop [expr {[yc $row] - $yscreen}]
1961 set yf [expr {$ytop * 1.0 / $ymax}]
1963 allcanvs yview moveto $yf
1967 } elseif {$selid ne {}} {
1968 set pending_select $selid
1970 set row [first_real_row]
1971 if {$row < $numcommits} {
1978 if {$phase eq "getcommits"} {
1979 show_status "Reading commits..."
1982 } elseif {$numcommits == 0} {
1983 show_status "No commits selected"
1987 # Stuff relating to the highlighting facility
1989 proc ishighlighted {row} {
1990 global vhighlights fhighlights nhighlights rhighlights
1992 if {[info exists nhighlights($row)] && $nhighlights($row) > 0} {
1993 return $nhighlights($row)
1995 if {[info exists vhighlights($row)] && $vhighlights($row) > 0} {
1996 return $vhighlights($row)
1998 if {[info exists fhighlights($row)] && $fhighlights($row) > 0} {
1999 return $fhighlights($row)
2001 if {[info exists rhighlights($row)] && $rhighlights($row) > 0} {
2002 return $rhighlights($row)
2007 proc bolden {row font} {
2008 global canv linehtag selectedline boldrows
2010 lappend boldrows $row
2011 $canv itemconf $linehtag($row) -font $font
2012 if {[info exists selectedline] && $row == $selectedline} {
2014 set t [eval $canv create rect [$canv bbox $linehtag($row)] \
2015 -outline {{}} -tags secsel \
2016 -fill [$canv cget -selectbackground]]
2021 proc bolden_name {row font} {
2022 global canv2 linentag selectedline boldnamerows
2024 lappend boldnamerows $row
2025 $canv2 itemconf $linentag($row) -font $font
2026 if {[info exists selectedline] && $row == $selectedline} {
2027 $canv2 delete secsel
2028 set t [eval $canv2 create rect [$canv2 bbox $linentag($row)] \
2029 -outline {{}} -tags secsel \
2030 -fill [$canv2 cget -selectbackground]]
2036 global mainfont boldrows
2039 foreach row $boldrows {
2040 if {![ishighlighted $row]} {
2041 bolden $row $mainfont
2043 lappend stillbold $row
2046 set boldrows $stillbold
2049 proc addvhighlight {n} {
2050 global hlview curview viewdata vhl_done vhighlights commitidx
2052 if {[info exists hlview]} {
2056 if {$n != $curview && ![info exists viewdata($n)]} {
2057 set viewdata($n) [list getcommits {{}} {{}} {} {} {} 0 0 0 {}]
2058 set vparentlist($n) {}
2059 set vdisporder($n) {}
2060 set vcmitlisted($n) {}
2063 set vhl_done $commitidx($hlview)
2064 if {$vhl_done > 0} {
2069 proc delvhighlight {} {
2070 global hlview vhighlights
2072 if {![info exists hlview]} return
2074 catch {unset vhighlights}
2078 proc vhighlightmore {} {
2079 global hlview vhl_done commitidx vhighlights
2080 global displayorder vdisporder curview mainfont
2082 set font [concat $mainfont bold]
2083 set max $commitidx($hlview)
2084 if {$hlview == $curview} {
2085 set disp $displayorder
2087 set disp $vdisporder($hlview)
2089 set vr [visiblerows]
2090 set r0 [lindex $vr 0]
2091 set r1 [lindex $vr 1]
2092 for {set i $vhl_done} {$i < $max} {incr i} {
2093 set id [lindex $disp $i]
2094 if {[info exists commitrow($curview,$id)]} {
2095 set row $commitrow($curview,$id)
2096 if {$r0 <= $row && $row <= $r1} {
2097 if {![highlighted $row]} {
2100 set vhighlights($row) 1
2107 proc askvhighlight {row id} {
2108 global hlview vhighlights commitrow iddrawn mainfont
2110 if {[info exists commitrow($hlview,$id)]} {
2111 if {[info exists iddrawn($id)] && ![ishighlighted $row]} {
2112 bolden $row [concat $mainfont bold]
2114 set vhighlights($row) 1
2116 set vhighlights($row) 0
2120 proc hfiles_change {name ix op} {
2121 global highlight_files filehighlight fhighlights fh_serial
2122 global mainfont highlight_paths
2124 if {[info exists filehighlight]} {
2125 # delete previous highlights
2126 catch {close $filehighlight}
2128 catch {unset fhighlights}
2130 unhighlight_filelist
2132 set highlight_paths {}
2133 after cancel do_file_hl $fh_serial
2135 if {$highlight_files ne {}} {
2136 after 300 do_file_hl $fh_serial
2140 proc makepatterns {l} {
2143 set ee [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} $e]
2144 if {[string index $ee end] eq "/"} {
2154 proc do_file_hl {serial} {
2155 global highlight_files filehighlight highlight_paths gdttype fhl_list
2157 if {$gdttype eq "touching paths:"} {
2158 if {[catch {set paths [shellsplit $highlight_files]}]} return
2159 set highlight_paths [makepatterns $paths]
2161 set gdtargs [concat -- $paths]
2163 set gdtargs [list "-S$highlight_files"]
2165 set cmd [concat | git diff-tree -r -s --stdin $gdtargs]
2166 set filehighlight [open $cmd r+]
2167 fconfigure $filehighlight -blocking 0
2168 filerun $filehighlight readfhighlight
2174 proc flushhighlights {} {
2175 global filehighlight fhl_list
2177 if {[info exists filehighlight]} {
2179 puts $filehighlight ""
2180 flush $filehighlight
2184 proc askfilehighlight {row id} {
2185 global filehighlight fhighlights fhl_list
2187 lappend fhl_list $id
2188 set fhighlights($row) -1
2189 puts $filehighlight $id
2192 proc readfhighlight {} {
2193 global filehighlight fhighlights commitrow curview mainfont iddrawn
2196 if {![info exists filehighlight]} {
2200 while {[incr nr] <= 100 && [gets $filehighlight line] >= 0} {
2201 set line [string trim $line]
2202 set i [lsearch -exact $fhl_list $line]
2203 if {$i < 0} continue
2204 for {set j 0} {$j < $i} {incr j} {
2205 set id [lindex $fhl_list $j]
2206 if {[info exists commitrow($curview,$id)]} {
2207 set fhighlights($commitrow($curview,$id)) 0
2210 set fhl_list [lrange $fhl_list [expr {$i+1}] end]
2211 if {$line eq {}} continue
2212 if {![info exists commitrow($curview,$line)]} continue
2213 set row $commitrow($curview,$line)
2214 if {[info exists iddrawn($line)] && ![ishighlighted $row]} {
2215 bolden $row [concat $mainfont bold]
2217 set fhighlights($row) 1
2219 if {[eof $filehighlight]} {
2221 puts "oops, git diff-tree died"
2222 catch {close $filehighlight}
2230 proc find_change {name ix op} {
2231 global nhighlights mainfont boldnamerows
2232 global findstring findpattern findtype
2234 # delete previous highlights, if any
2235 foreach row $boldnamerows {
2236 bolden_name $row $mainfont
2239 catch {unset nhighlights}
2242 if {$findtype ne "Regexp"} {
2243 set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \
2245 set findpattern "*$e*"
2250 proc doesmatch {f} {
2251 global findtype findstring findpattern
2253 if {$findtype eq "Regexp"} {
2254 return [regexp $findstring $f]
2255 } elseif {$findtype eq "IgnCase"} {
2256 return [string match -nocase $findpattern $f]
2258 return [string match $findpattern $f]
2262 proc askfindhighlight {row id} {
2263 global nhighlights commitinfo iddrawn mainfont
2265 global markingmatches
2267 if {![info exists commitinfo($id)]} {
2270 set info $commitinfo($id)
2272 set fldtypes {Headline Author Date Committer CDate Comments}
2273 foreach f $info ty $fldtypes {
2274 if {($findloc eq "All fields" || $findloc eq $ty) &&
2276 if {$ty eq "Author"} {
2283 if {$isbold && [info exists iddrawn($id)]} {
2284 set f [concat $mainfont bold]
2285 if {![ishighlighted $row]} {
2291 if {$markingmatches} {
2292 markrowmatches $row $id
2295 set nhighlights($row) $isbold
2298 proc markrowmatches {row id} {
2299 global canv canv2 linehtag linentag commitinfo findloc
2301 set headline [lindex $commitinfo($id) 0]
2302 set author [lindex $commitinfo($id) 1]
2303 $canv delete match$row
2304 $canv2 delete match$row
2305 if {$findloc eq "All fields" || $findloc eq "Headline"} {
2306 set m [findmatches $headline]
2308 markmatches $canv $row $headline $linehtag($row) $m \
2309 [$canv itemcget $linehtag($row) -font] $row
2312 if {$findloc eq "All fields" || $findloc eq "Author"} {
2313 set m [findmatches $author]
2315 markmatches $canv2 $row $author $linentag($row) $m \
2316 [$canv2 itemcget $linentag($row) -font] $row
2321 proc vrel_change {name ix op} {
2322 global highlight_related
2325 if {$highlight_related ne "None"} {
2330 # prepare for testing whether commits are descendents or ancestors of a
2331 proc rhighlight_sel {a} {
2332 global descendent desc_todo ancestor anc_todo
2333 global highlight_related rhighlights
2335 catch {unset descendent}
2336 set desc_todo [list $a]
2337 catch {unset ancestor}
2338 set anc_todo [list $a]
2339 if {$highlight_related ne "None"} {
2345 proc rhighlight_none {} {
2348 catch {unset rhighlights}
2352 proc is_descendent {a} {
2353 global curview children commitrow descendent desc_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 {$commitrow($v,$do) < $la} {
2363 lappend leftover $do
2366 foreach nk $children($v,$do) {
2367 if {![info exists descendent($nk)]} {
2368 set descendent($nk) 1
2376 set desc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
2380 set descendent($a) 0
2381 set desc_todo $leftover
2384 proc is_ancestor {a} {
2385 global curview parentlist commitrow ancestor anc_todo
2388 set la $commitrow($v,$a)
2392 for {set i 0} {$i < [llength $todo]} {incr i} {
2393 set do [lindex $todo $i]
2394 if {![info exists commitrow($v,$do)] || $commitrow($v,$do) > $la} {
2395 lappend leftover $do
2398 foreach np [lindex $parentlist $commitrow($v,$do)] {
2399 if {![info exists ancestor($np)]} {
2408 set anc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
2413 set anc_todo $leftover
2416 proc askrelhighlight {row id} {
2417 global descendent highlight_related iddrawn mainfont rhighlights
2418 global selectedline ancestor
2420 if {![info exists selectedline]} return
2422 if {$highlight_related eq "Descendent" ||
2423 $highlight_related eq "Not descendent"} {
2424 if {![info exists descendent($id)]} {
2427 if {$descendent($id) == ($highlight_related eq "Descendent")} {
2430 } elseif {$highlight_related eq "Ancestor" ||
2431 $highlight_related eq "Not ancestor"} {
2432 if {![info exists ancestor($id)]} {
2435 if {$ancestor($id) == ($highlight_related eq "Ancestor")} {
2439 if {[info exists iddrawn($id)]} {
2440 if {$isbold && ![ishighlighted $row]} {
2441 bolden $row [concat $mainfont bold]
2444 set rhighlights($row) $isbold
2447 proc next_hlcont {} {
2448 global fhl_row fhl_dirn displayorder numcommits
2449 global vhighlights fhighlights nhighlights rhighlights
2450 global hlview filehighlight findstring highlight_related
2452 if {![info exists fhl_dirn] || $fhl_dirn == 0} return
2455 if {$row < 0 || $row >= $numcommits} {
2460 set id [lindex $displayorder $row]
2461 if {[info exists hlview]} {
2462 if {![info exists vhighlights($row)]} {
2463 askvhighlight $row $id
2465 if {$vhighlights($row) > 0} break
2467 if {$findstring ne {}} {
2468 if {![info exists nhighlights($row)]} {
2469 askfindhighlight $row $id
2471 if {$nhighlights($row) > 0} break
2473 if {$highlight_related ne "None"} {
2474 if {![info exists rhighlights($row)]} {
2475 askrelhighlight $row $id
2477 if {$rhighlights($row) > 0} break
2479 if {[info exists filehighlight]} {
2480 if {![info exists fhighlights($row)]} {
2481 # ask for a few more while we're at it...
2483 for {set n 0} {$n < 100} {incr n} {
2484 if {![info exists fhighlights($r)]} {
2485 askfilehighlight $r [lindex $displayorder $r]
2488 if {$r < 0 || $r >= $numcommits} break
2492 if {$fhighlights($row) < 0} {
2496 if {$fhighlights($row) > 0} break
2504 proc next_highlight {dirn} {
2505 global selectedline fhl_row fhl_dirn
2506 global hlview filehighlight findstring highlight_related
2508 if {![info exists selectedline]} return
2509 if {!([info exists hlview] || $findstring ne {} ||
2510 $highlight_related ne "None" || [info exists filehighlight])} return
2511 set fhl_row [expr {$selectedline + $dirn}]
2516 proc cancel_next_highlight {} {
2522 # Graph layout functions
2524 proc shortids {ids} {
2527 if {[llength $id] > 1} {
2528 lappend res [shortids $id]
2529 } elseif {[regexp {^[0-9a-f]{40}$} $id]} {
2530 lappend res [string range $id 0 7]
2538 proc incrange {l x o} {
2541 set e [lindex $l $x]
2543 lset l $x [expr {$e + $o}]
2552 for {} {$n > 0} {incr n -1} {
2558 proc usedinrange {id l1 l2} {
2559 global children commitrow curview
2561 if {[info exists commitrow($curview,$id)]} {
2562 set r $commitrow($curview,$id)
2563 if {$l1 <= $r && $r <= $l2} {
2564 return [expr {$r - $l1 + 1}]
2567 set kids $children($curview,$id)
2569 set r $commitrow($curview,$c)
2570 if {$l1 <= $r && $r <= $l2} {
2571 return [expr {$r - $l1 + 1}]
2577 # Work out where id should go in idlist so that order-token
2578 # values increase from left to right
2579 proc idcol {idlist id {i 0}} {
2580 global ordertok curview
2582 set t $ordertok($curview,$id)
2583 if {$i >= [llength $idlist] ||
2584 $t < $ordertok($curview,[lindex $idlist $i])} {
2585 if {$i > [llength $idlist]} {
2586 set i [llength $idlist]
2588 while {[incr i -1] >= 0 &&
2589 $t < $ordertok($curview,[lindex $idlist $i])} {}
2592 if {$t > $ordertok($curview,[lindex $idlist $i])} {
2593 while {[incr i] < [llength $idlist] &&
2594 $t >= $ordertok($curview,[lindex $idlist $i])} {}
2600 proc makeuparrow {oid y x} {
2601 global rowidlist uparrowlen idrowranges displayorder
2603 for {set i 1} {$i < $uparrowlen && $y > 1} {incr i} {
2605 set idl [lindex $rowidlist $y]
2606 set x [idcol $idl $oid $x]
2607 lset rowidlist $y [linsert $idl $x $oid]
2609 lappend idrowranges($oid) [lindex $displayorder $y]
2612 proc initlayout {} {
2613 global rowidlist displayorder commitlisted
2614 global rowlaidout rowoptim
2615 global idinlist rowchk rowrangelist idrowranges
2616 global numcommits canvxmax canv
2619 global colormap rowtextx
2629 catch {unset idinlist}
2630 catch {unset rowchk}
2633 set canvxmax [$canv cget -width]
2634 catch {unset colormap}
2635 catch {unset rowtextx}
2636 catch {unset idrowranges}
2640 proc setcanvscroll {} {
2641 global canv canv2 canv3 numcommits linespc canvxmax canvy0
2643 set ymax [expr {$canvy0 + ($numcommits - 0.5) * $linespc + 2}]
2644 $canv conf -scrollregion [list 0 0 $canvxmax $ymax]
2645 $canv2 conf -scrollregion [list 0 0 0 $ymax]
2646 $canv3 conf -scrollregion [list 0 0 0 $ymax]
2649 proc visiblerows {} {
2650 global canv numcommits linespc
2652 set ymax [lindex [$canv cget -scrollregion] 3]
2653 if {$ymax eq {} || $ymax == 0} return
2655 set y0 [expr {int([lindex $f 0] * $ymax)}]
2656 set r0 [expr {int(($y0 - 3) / $linespc) - 1}]
2660 set y1 [expr {int([lindex $f 1] * $ymax)}]
2661 set r1 [expr {int(($y1 - 3) / $linespc) + 1}]
2662 if {$r1 >= $numcommits} {
2663 set r1 [expr {$numcommits - 1}]
2665 return [list $r0 $r1]
2668 proc layoutmore {tmax allread} {
2669 global rowlaidout rowoptim commitidx numcommits optim_delay
2670 global uparrowlen curview rowidlist idinlist
2673 set showdelay $optim_delay
2674 set optdelay [expr {$uparrowlen + 1}]
2676 if {$rowoptim - $showdelay > $numcommits} {
2677 showstuff [expr {$rowoptim - $showdelay}] $showlast
2678 } elseif {$rowlaidout - $optdelay > $rowoptim} {
2679 set nr [expr {$rowlaidout - $optdelay - $rowoptim}]
2683 optimize_rows $rowoptim 0 [expr {$rowoptim + $nr}]
2685 } elseif {$commitidx($curview) > $rowlaidout} {
2686 set nr [expr {$commitidx($curview) - $rowlaidout}]
2687 # may need to increase this threshold if uparrowlen or
2688 # mingaplen are increased...
2693 set rowlaidout [layoutrows $row [expr {$row + $nr}] $allread]
2694 if {$rowlaidout == $row} {
2697 } elseif {$allread} {
2699 set nrows $commitidx($curview)
2700 if {[lindex $rowidlist $nrows] ne {} ||
2701 [array names idinlist] ne {}} {
2703 set rowlaidout $commitidx($curview)
2704 } elseif {$rowoptim == $nrows} {
2707 if {$numcommits == $nrows} {
2714 if {$tmax ne {} && [clock clicks -milliseconds] >= $tmax} {
2720 proc showstuff {canshow last} {
2721 global numcommits commitrow pending_select selectedline curview
2722 global lookingforhead mainheadid displayorder selectfirst
2723 global lastscrollset
2725 if {$numcommits == 0} {
2727 set phase "incrdraw"
2731 set prev $numcommits
2732 set numcommits $canshow
2733 set t [clock clicks -milliseconds]
2734 if {$prev < 100 || $last || $t - $lastscrollset > 500} {
2735 set lastscrollset $t
2738 set rows [visiblerows]
2739 set r1 [lindex $rows 1]
2740 if {$r1 >= $canshow} {
2741 set r1 [expr {$canshow - 1}]
2746 if {[info exists pending_select] &&
2747 [info exists commitrow($curview,$pending_select)] &&
2748 $commitrow($curview,$pending_select) < $numcommits} {
2749 selectline $commitrow($curview,$pending_select) 1
2752 if {[info exists selectedline] || [info exists pending_select]} {
2755 set l [first_real_row]
2760 if {$lookingforhead && [info exists commitrow($curview,$mainheadid)]
2761 && ($last || $commitrow($curview,$mainheadid) < $numcommits - 1)} {
2762 set lookingforhead 0
2767 proc doshowlocalchanges {} {
2768 global lookingforhead curview mainheadid phase commitrow
2770 if {[info exists commitrow($curview,$mainheadid)] &&
2771 ($phase eq {} || $commitrow($curview,$mainheadid) < $numcommits - 1)} {
2773 } elseif {$phase ne {}} {
2774 set lookingforhead 1
2778 proc dohidelocalchanges {} {
2779 global lookingforhead localfrow localirow lserial
2781 set lookingforhead 0
2782 if {$localfrow >= 0} {
2783 removerow $localfrow
2785 if {$localirow > 0} {
2789 if {$localirow >= 0} {
2790 removerow $localirow
2796 # spawn off a process to do git diff-index --cached HEAD
2797 proc dodiffindex {} {
2798 global localirow localfrow lserial
2803 set fd [open "|git diff-index --cached HEAD" r]
2804 fconfigure $fd -blocking 0
2805 filerun $fd [list readdiffindex $fd $lserial]
2808 proc readdiffindex {fd serial} {
2809 global localirow commitrow mainheadid nullid2 curview
2810 global commitinfo commitdata lserial
2813 if {[gets $fd line] < 0} {
2819 # we only need to see one line and we don't really care what it says...
2822 # now see if there are any local changes not checked in to the index
2823 if {$serial == $lserial} {
2824 set fd [open "|git diff-files" r]
2825 fconfigure $fd -blocking 0
2826 filerun $fd [list readdifffiles $fd $serial]
2829 if {$isdiff && $serial == $lserial && $localirow == -1} {
2830 # add the line for the changes in the index to the graph
2831 set localirow $commitrow($curview,$mainheadid)
2832 set hl "Local changes checked in to index but not committed"
2833 set commitinfo($nullid2) [list $hl {} {} {} {} " $hl\n"]
2834 set commitdata($nullid2) "\n $hl\n"
2835 insertrow $localirow $nullid2
2840 proc readdifffiles {fd serial} {
2841 global localirow localfrow commitrow mainheadid nullid curview
2842 global commitinfo commitdata lserial
2845 if {[gets $fd line] < 0} {
2851 # we only need to see one line and we don't really care what it says...
2854 if {$isdiff && $serial == $lserial && $localfrow == -1} {
2855 # add the line for the local diff to the graph
2856 if {$localirow >= 0} {
2857 set localfrow $localirow
2860 set localfrow $commitrow($curview,$mainheadid)
2862 set hl "Local uncommitted changes, not checked in to index"
2863 set commitinfo($nullid) [list $hl {} {} {} {} " $hl\n"]
2864 set commitdata($nullid) "\n $hl\n"
2865 insertrow $localfrow $nullid
2870 proc layoutrows {row endrow last} {
2871 global rowidlist displayorder
2872 global uparrowlen downarrowlen maxwidth mingaplen
2873 global children parentlist
2875 global commitidx curview
2876 global idinlist rowchk rowrangelist
2878 set idlist [lindex $rowidlist $row]
2879 while {$row < $endrow} {
2880 set id [lindex $displayorder $row]
2883 set olds [lindex $parentlist $row]
2885 if {![info exists idinlist($p)]} {
2887 } elseif {!$idinlist($p)} {
2891 set nev [expr {[llength $idlist] + [llength $newolds]
2892 + [llength $oldolds] - $maxwidth + 1}]
2893 if {1 || $nev > 0} {
2895 $row + $uparrowlen + $mingaplen >= $commitidx($curview)} break
2896 for {set x [llength $idlist]} {[incr x -1] >= 0} {} {
2897 set i [lindex $idlist $x]
2898 if {![info exists rowchk($i)] || $row >= $rowchk($i)} {
2899 set r [usedinrange $i [expr {$row - $downarrowlen}] \
2900 [expr {$row + $uparrowlen + $mingaplen}]]
2902 set idlist [lreplace $idlist $x $x]
2904 set rm1 [expr {$row - 1}]
2905 lappend idrowranges($i) [lindex $displayorder $rm1]
2906 #if {[incr nev -1] <= 0} break
2909 set rowchk($id) [expr {$row + $r}]
2912 lset rowidlist $row $idlist
2914 set col [lsearch -exact $idlist $id]
2916 set col [idcol $idlist $id]
2917 set idlist [linsert $idlist $col $id]
2918 lset rowidlist $row $idlist
2919 if {$children($curview,$id) ne {}} {
2921 makeuparrow $id $row $col
2927 if {[info exists idrowranges($id)]} {
2928 set ranges $idrowranges($id)
2930 unset idrowranges($id)
2932 lappend rowrangelist $ranges
2934 set idlist [lreplace $idlist $col $col]
2936 foreach i $newolds {
2937 set x [idcol $idlist $i $x]
2938 set idlist [linsert $idlist $x $i]
2940 set idrowranges($i) $id
2942 foreach oid $oldolds {
2943 set idinlist($oid) 1
2944 set x [idcol $idlist $oid $x]
2945 set idlist [linsert $idlist $x $oid]
2946 makeuparrow $oid $row $x
2948 lappend rowidlist $idlist
2953 proc addextraid {id row} {
2954 global displayorder commitrow commitinfo
2955 global commitidx commitlisted
2956 global parentlist children curview
2958 incr commitidx($curview)
2959 lappend displayorder $id
2960 lappend commitlisted 0
2961 lappend parentlist {}
2962 set commitrow($curview,$id) $row
2964 if {![info exists commitinfo($id)]} {
2965 set commitinfo($id) {"No commit information available"}
2967 if {![info exists children($curview,$id)]} {
2968 set children($curview,$id) {}
2972 proc layouttail {} {
2973 global rowidlist idinlist commitidx curview
2974 global idrowranges rowrangelist
2976 set row $commitidx($curview)
2977 set idlist [lindex $rowidlist $row]
2978 while {$idlist ne {}} {
2979 set col [expr {[llength $idlist] - 1}]
2980 set id [lindex $idlist $col]
2983 lappend idrowranges($id) $id
2984 lappend rowrangelist $idrowranges($id)
2985 unset idrowranges($id)
2987 set idlist [lreplace $idlist $col $col]
2988 lappend rowidlist $idlist
2991 foreach id [array names idinlist] {
2994 lset rowidlist $row [list $id]
2995 makeuparrow $id $row 0
2996 lappend idrowranges($id) $id
2997 lappend rowrangelist $idrowranges($id)
2998 unset idrowranges($id)
3000 lappend rowidlist {}
3004 proc insert_pad {row col npad} {
3007 set pad [ntimes $npad {}]
3008 lset rowidlist $row [eval linsert [list [lindex $rowidlist $row]] $col $pad]
3011 proc optimize_rows {row col endrow} {
3012 global rowidlist displayorder
3017 set idlist [lindex $rowidlist [expr {$row - 1}]]
3019 set previdlist [lindex $rowidlist [expr {$row - 2}]]
3023 for {} {$row < $endrow} {incr row} {
3024 set pprevidlist $previdlist
3025 set previdlist $idlist
3026 set idlist [lindex $rowidlist $row]
3028 set y0 [expr {$row - 1}]
3029 set ym [expr {$row - 2}]
3032 for {} {$col < [llength $idlist]} {incr col} {
3033 set id [lindex $idlist $col]
3034 if {[lindex $previdlist $col] eq $id} continue
3039 set x0 [lsearch -exact $previdlist $id]
3040 if {$x0 < 0} continue
3041 set z [expr {$x0 - $col}]
3045 set xm [lsearch -exact $pprevidlist $id]
3047 set z0 [expr {$xm - $x0}]
3051 set ranges [rowranges $id]
3052 if {$ranges ne {} && $y0 > [lindex $ranges 0]} {
3056 # Looking at lines from this row to the previous row,
3057 # make them go straight up if they end in an arrow on
3058 # the previous row; otherwise make them go straight up
3060 if {$z < -1 || ($z < 0 && $isarrow)} {
3061 # Line currently goes left too much;
3062 # insert pads in the previous row, then optimize it
3063 set npad [expr {-1 - $z + $isarrow}]
3064 insert_pad $y0 $x0 $npad
3066 optimize_rows $y0 $x0 $row
3068 set previdlist [lindex $rowidlist $y0]
3069 set x0 [lsearch -exact $previdlist $id]
3070 set z [expr {$x0 - $col}]
3072 set pprevidlist [lindex $rowidlist $ym]
3073 set xm [lsearch -exact $pprevidlist $id]
3074 set z0 [expr {$xm - $x0}]
3076 } elseif {$z > 1 || ($z > 0 && $isarrow)} {
3077 # Line currently goes right too much;
3078 # insert pads in this line
3079 set npad [expr {$z - 1 + $isarrow}]
3080 set pad [ntimes $npad {}]
3081 set idlist [eval linsert \$idlist $col $pad]
3083 set z [expr {$x0 - $col}]
3086 if {$z0 eq {} && !$isarrow && $ym >= 0} {
3087 # this line links to its first child on row $row-2
3088 set id [lindex $displayorder $ym]
3089 set xc [lsearch -exact $pprevidlist $id]
3091 set z0 [expr {$xc - $x0}]
3094 # avoid lines jigging left then immediately right
3095 if {$z0 ne {} && $z < 0 && $z0 > 0} {
3096 insert_pad $y0 $x0 1
3098 optimize_rows $y0 $x0 $row
3099 set previdlist [lindex $rowidlist $y0]
3100 set pprevidlist [lindex $rowidlist $ym]
3104 # Find the first column that doesn't have a line going right
3105 for {set col [llength $idlist]} {[incr col -1] >= 0} {} {
3106 set id [lindex $idlist $col]
3107 if {$id eq {}} break
3108 set x0 [lsearch -exact $previdlist $id]
3110 # check if this is the link to the first child
3111 set ranges [rowranges $id]
3112 if {$ranges ne {} && $row == [lindex $ranges 0]} {
3113 # it is, work out offset to child
3114 set id [lindex $displayorder $y0]
3115 set x0 [lsearch -exact $previdlist $id]
3118 if {$x0 <= $col} break
3120 # Insert a pad at that column as long as it has a line and
3121 # isn't the last column
3122 if {$x0 >= 0 && [incr col] < [llength $idlist]} {
3123 set idlist [linsert $idlist $col {}]
3126 lset rowidlist $row $idlist
3132 global canvx0 linespc
3133 return [expr {$canvx0 + $col * $linespc}]
3137 global canvy0 linespc
3138 return [expr {$canvy0 + $row * $linespc}]
3141 proc linewidth {id} {
3142 global thickerline lthickness
3145 if {[info exists thickerline] && $id eq $thickerline} {
3146 set wid [expr {2 * $lthickness}]
3151 proc rowranges {id} {
3152 global phase idrowranges commitrow rowlaidout rowrangelist curview
3156 ([info exists commitrow($curview,$id)]
3157 && $commitrow($curview,$id) < $rowlaidout)} {
3158 set ranges [lindex $rowrangelist $commitrow($curview,$id)]
3159 } elseif {[info exists idrowranges($id)]} {
3160 set ranges $idrowranges($id)
3163 foreach rid $ranges {
3164 lappend linenos $commitrow($curview,$rid)
3166 if {$linenos ne {}} {
3167 lset linenos 0 [expr {[lindex $linenos 0] + 1}]
3172 # work around tk8.4 refusal to draw arrows on diagonal segments
3173 proc adjarrowhigh {coords} {
3176 set x0 [lindex $coords 0]
3177 set x1 [lindex $coords 2]
3179 set y0 [lindex $coords 1]
3180 set y1 [lindex $coords 3]
3181 if {$y0 - $y1 <= 2 * $linespc && $x1 == [lindex $coords 4]} {
3182 # we have a nearby vertical segment, just trim off the diag bit
3183 set coords [lrange $coords 2 end]
3185 set slope [expr {($x0 - $x1) / ($y0 - $y1)}]
3186 set xi [expr {$x0 - $slope * $linespc / 2}]
3187 set yi [expr {$y0 - $linespc / 2}]
3188 set coords [lreplace $coords 0 1 $xi $y0 $xi $yi]
3194 proc drawlineseg {id row endrow arrowlow} {
3195 global rowidlist displayorder iddrawn linesegs
3196 global canv colormap linespc curview maxlinelen
3198 set cols [list [lsearch -exact [lindex $rowidlist $row] $id]]
3199 set le [expr {$row + 1}]
3202 set c [lsearch -exact [lindex $rowidlist $le] $id]
3208 set x [lindex $displayorder $le]
3213 if {[info exists iddrawn($x)] || $le == $endrow} {
3214 set c [lsearch -exact [lindex $rowidlist [expr {$le+1}]] $id]
3230 if {[info exists linesegs($id)]} {
3231 set lines $linesegs($id)
3233 set r0 [lindex $li 0]
3235 if {$r0 == $le && [lindex $li 1] - $row <= $maxlinelen} {
3245 set li [lindex $lines [expr {$i-1}]]
3246 set r1 [lindex $li 1]
3247 if {$r1 == $row && $le - [lindex $li 0] <= $maxlinelen} {
3252 set x [lindex $cols [expr {$le - $row}]]
3253 set xp [lindex $cols [expr {$le - 1 - $row}]]
3254 set dir [expr {$xp - $x}]
3256 set ith [lindex $lines $i 2]
3257 set coords [$canv coords $ith]
3258 set ah [$canv itemcget $ith -arrow]
3259 set arrowhigh [expr {$ah eq "first" || $ah eq "both"}]
3260 set x2 [lindex $cols [expr {$le + 1 - $row}]]
3261 if {$x2 ne {} && $x - $x2 == $dir} {
3262 set coords [lrange $coords 0 end-2]
3265 set coords [list [xc $le $x] [yc $le]]
3268 set itl [lindex $lines [expr {$i-1}] 2]
3269 set al [$canv itemcget $itl -arrow]
3270 set arrowlow [expr {$al eq "last" || $al eq "both"}]
3271 } elseif {$arrowlow &&
3272 [lsearch -exact [lindex $rowidlist [expr {$row-1}]] $id] >= 0} {
3275 set arrow [lindex {none first last both} [expr {$arrowhigh + 2*$arrowlow}]]
3276 for {set y $le} {[incr y -1] > $row} {} {
3278 set xp [lindex $cols [expr {$y - 1 - $row}]]
3279 set ndir [expr {$xp - $x}]
3280 if {$dir != $ndir || $xp < 0} {
3281 lappend coords [xc $y $x] [yc $y]
3287 # join parent line to first child
3288 set ch [lindex $displayorder $row]
3289 set xc [lsearch -exact [lindex $rowidlist $row] $ch]
3291 puts "oops: drawlineseg: child $ch not on row $row"
3294 lappend coords [xc $row [expr {$x-1}]] [yc $row]
3295 } elseif {$xc > $x + 1} {
3296 lappend coords [xc $row [expr {$x+1}]] [yc $row]
3300 lappend coords [xc $row $x] [yc $row]
3302 set xn [xc $row $xp]
3304 # work around tk8.4 refusal to draw arrows on diagonal segments
3305 if {$arrowlow && $xn != [lindex $coords end-1]} {
3306 if {[llength $coords] < 4 ||
3307 [lindex $coords end-3] != [lindex $coords end-1] ||
3308 [lindex $coords end] - $yn > 2 * $linespc} {
3309 set xn [xc $row [expr {$xp - 0.5 * $dir}]]
3310 set yo [yc [expr {$row + 0.5}]]
3311 lappend coords $xn $yo $xn $yn
3314 lappend coords $xn $yn
3319 set coords [adjarrowhigh $coords]
3322 set t [$canv create line $coords -width [linewidth $id] \
3323 -fill $colormap($id) -tags lines.$id -arrow $arrow]
3326 set lines [linsert $lines $i [list $row $le $t]]
3328 $canv coords $ith $coords
3329 if {$arrow ne $ah} {
3330 $canv itemconf $ith -arrow $arrow
3332 lset lines $i 0 $row
3335 set xo [lsearch -exact [lindex $rowidlist [expr {$row - 1}]] $id]
3336 set ndir [expr {$xo - $xp}]
3337 set clow [$canv coords $itl]
3338 if {$dir == $ndir} {
3339 set clow [lrange $clow 2 end]
3341 set coords [concat $coords $clow]
3343 lset lines [expr {$i-1}] 1 $le
3345 set coords [adjarrowhigh $coords]
3348 # coalesce two pieces
3350 set b [lindex $lines [expr {$i-1}] 0]
3351 set e [lindex $lines $i 1]
3352 set lines [lreplace $lines [expr {$i-1}] $i [list $b $e $itl]]
3354 $canv coords $itl $coords
3355 if {$arrow ne $al} {
3356 $canv itemconf $itl -arrow $arrow
3360 set linesegs($id) $lines
3364 proc drawparentlinks {id row} {
3365 global rowidlist canv colormap curview parentlist
3366 global idpos linespc
3368 set rowids [lindex $rowidlist $row]
3369 set col [lsearch -exact $rowids $id]
3370 if {$col < 0} return
3371 set olds [lindex $parentlist $row]
3372 set row2 [expr {$row + 1}]
3373 set x [xc $row $col]
3376 set d [expr {int(0.4 * $linespc)}]
3377 set ymid [expr {$y + $d}]
3378 set ids [lindex $rowidlist $row2]
3379 # rmx = right-most X coord used
3382 set i [lsearch -exact $ids $p]
3384 puts "oops, parent $p of $id not in list"
3387 set x2 [xc $row2 $i]
3391 set j [lsearch -exact $rowids $p]
3393 # drawlineseg will do this one for us
3397 # should handle duplicated parents here...
3398 set coords [list $x $y]
3400 # if attaching to a vertical segment, draw a smaller
3401 # slant for visual distinctness
3404 lappend coords [expr {$x2 + $d}] $y $x2 $ymid
3406 lappend coords [expr {$x2 - $d}] $y $x2 $ymid
3408 } elseif {$i < $col && $i < $j} {
3409 # segment slants towards us already
3410 lappend coords [xc $row $j] $y
3412 if {$i < $col - 1} {
3413 lappend coords [expr {$x2 + $linespc}] $y
3414 } elseif {$i > $col + 1} {
3415 lappend coords [expr {$x2 - $linespc}] $y
3417 lappend coords $x2 $y2
3420 lappend coords $x2 $y2
3422 set t [$canv create line $coords -width [linewidth $p] \
3423 -fill $colormap($p) -tags lines.$p]
3427 if {$rmx > [lindex $idpos($id) 1]} {
3428 lset idpos($id) 1 $rmx
3433 proc drawlines {id} {
3436 $canv itemconf lines.$id -width [linewidth $id]
3439 proc drawcmittext {id row col} {
3440 global linespc canv canv2 canv3 canvy0 fgcolor curview
3441 global commitlisted commitinfo rowidlist parentlist
3442 global rowtextx idpos idtags idheads idotherrefs
3443 global linehtag linentag linedtag
3444 global mainfont canvxmax boldrows boldnamerows fgcolor nullid nullid2
3446 # listed is 0 for boundary, 1 for normal, 2 for left, 3 for right
3447 set listed [lindex $commitlisted $row]
3448 if {$id eq $nullid} {
3450 } elseif {$id eq $nullid2} {
3453 set ofill [expr {$listed != 0? "blue": "white"}]
3455 set x [xc $row $col]
3457 set orad [expr {$linespc / 3}]
3459 set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \
3460 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
3461 -fill $ofill -outline $fgcolor -width 1 -tags circle]
3462 } elseif {$listed == 2} {
3463 # triangle pointing left for left-side commits
3464 set t [$canv create polygon \
3465 [expr {$x - $orad}] $y \
3466 [expr {$x + $orad - 1}] [expr {$y - $orad}] \
3467 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
3468 -fill $ofill -outline $fgcolor -width 1 -tags circle]
3470 # triangle pointing right for right-side commits
3471 set t [$canv create polygon \
3472 [expr {$x + $orad - 1}] $y \
3473 [expr {$x - $orad}] [expr {$y - $orad}] \
3474 [expr {$x - $orad}] [expr {$y + $orad - 1}] \
3475 -fill $ofill -outline $fgcolor -width 1 -tags circle]
3478 $canv bind $t <1> {selcanvline {} %x %y}
3479 set rmx [llength [lindex $rowidlist $row]]
3480 set olds [lindex $parentlist $row]
3482 set nextids [lindex $rowidlist [expr {$row + 1}]]
3484 set i [lsearch -exact $nextids $p]
3490 set xt [xc $row $rmx]
3491 set rowtextx($row) $xt
3492 set idpos($id) [list $x $xt $y]
3493 if {[info exists idtags($id)] || [info exists idheads($id)]
3494 || [info exists idotherrefs($id)]} {
3495 set xt [drawtags $id $x $xt $y]
3497 set headline [lindex $commitinfo($id) 0]
3498 set name [lindex $commitinfo($id) 1]
3499 set date [lindex $commitinfo($id) 2]
3500 set date [formatdate $date]
3503 set isbold [ishighlighted $row]
3505 lappend boldrows $row
3508 lappend boldnamerows $row
3512 set linehtag($row) [$canv create text $xt $y -anchor w -fill $fgcolor \
3513 -text $headline -font $font -tags text]
3514 $canv bind $linehtag($row) <Button-3> "rowmenu %X %Y $id"
3515 set linentag($row) [$canv2 create text 3 $y -anchor w -fill $fgcolor \
3516 -text $name -font $nfont -tags text]
3517 set linedtag($row) [$canv3 create text 3 $y -anchor w -fill $fgcolor \
3518 -text $date -font $mainfont -tags text]
3519 set xr [expr {$xt + [font measure $mainfont $headline]}]
3520 if {$xr > $canvxmax} {
3526 proc drawcmitrow {row} {
3527 global displayorder rowidlist
3528 global iddrawn markingmatches
3529 global commitinfo parentlist numcommits
3530 global filehighlight fhighlights findstring nhighlights
3531 global hlview vhighlights
3532 global highlight_related rhighlights
3534 if {$row >= $numcommits} return
3536 set id [lindex $displayorder $row]
3537 if {[info exists hlview] && ![info exists vhighlights($row)]} {
3538 askvhighlight $row $id
3540 if {[info exists filehighlight] && ![info exists fhighlights($row)]} {
3541 askfilehighlight $row $id
3543 if {$findstring ne {} && ![info exists nhighlights($row)]} {
3544 askfindhighlight $row $id
3546 if {$highlight_related ne "None" && ![info exists rhighlights($row)]} {
3547 askrelhighlight $row $id
3549 if {![info exists iddrawn($id)]} {
3550 set col [lsearch -exact [lindex $rowidlist $row] $id]
3552 puts "oops, row $row id $id not in list"
3555 if {![info exists commitinfo($id)]} {
3559 drawcmittext $id $row $col
3562 if {$markingmatches} {
3563 markrowmatches $row $id
3567 proc drawcommits {row {endrow {}}} {
3568 global numcommits iddrawn displayorder curview
3569 global parentlist rowidlist
3574 if {$endrow eq {}} {
3577 if {$endrow >= $numcommits} {
3578 set endrow [expr {$numcommits - 1}]
3581 # make the lines join to already-drawn rows either side
3582 set r [expr {$row - 1}]
3583 if {$r < 0 || ![info exists iddrawn([lindex $displayorder $r])]} {
3586 set er [expr {$endrow + 1}]
3587 if {$er >= $numcommits ||
3588 ![info exists iddrawn([lindex $displayorder $er])]} {
3591 for {} {$r <= $er} {incr r} {
3592 set id [lindex $displayorder $r]
3593 set wasdrawn [info exists iddrawn($id)]
3595 if {$r == $er} break
3596 set nextid [lindex $displayorder [expr {$r + 1}]]
3597 if {$wasdrawn && [info exists iddrawn($nextid)]} {
3598 catch {unset prevlines}
3601 drawparentlinks $id $r
3603 if {[info exists lineends($r)]} {
3604 foreach lid $lineends($r) {
3605 unset prevlines($lid)
3608 set rowids [lindex $rowidlist $r]
3609 foreach lid $rowids {
3610 if {$lid eq {}} continue
3612 # see if this is the first child of any of its parents
3613 foreach p [lindex $parentlist $r] {
3614 if {[lsearch -exact $rowids $p] < 0} {
3615 # make this line extend up to the child
3616 set le [drawlineseg $p $r $er 0]
3617 lappend lineends($le) $p
3621 } elseif {![info exists prevlines($lid)]} {
3622 set le [drawlineseg $lid $r $er 1]
3623 lappend lineends($le) $lid
3624 set prevlines($lid) 1
3630 proc drawfrac {f0 f1} {
3633 set ymax [lindex [$canv cget -scrollregion] 3]
3634 if {$ymax eq {} || $ymax == 0} return
3635 set y0 [expr {int($f0 * $ymax)}]
3636 set row [expr {int(($y0 - 3) / $linespc) - 1}]
3637 set y1 [expr {int($f1 * $ymax)}]
3638 set endrow [expr {int(($y1 - 3) / $linespc) + 1}]
3639 drawcommits $row $endrow
3642 proc drawvisible {} {
3644 eval drawfrac [$canv yview]
3647 proc clear_display {} {
3648 global iddrawn linesegs
3649 global vhighlights fhighlights nhighlights rhighlights
3652 catch {unset iddrawn}
3653 catch {unset linesegs}
3654 catch {unset vhighlights}
3655 catch {unset fhighlights}
3656 catch {unset nhighlights}
3657 catch {unset rhighlights}
3660 proc findcrossings {id} {
3661 global rowidlist parentlist numcommits displayorder
3665 foreach {s e} [rowranges $id] {
3666 if {$e >= $numcommits} {
3667 set e [expr {$numcommits - 1}]
3669 if {$e <= $s} continue
3670 for {set row $e} {[incr row -1] >= $s} {} {
3671 set x [lsearch -exact [lindex $rowidlist $row] $id]
3673 set olds [lindex $parentlist $row]
3674 set kid [lindex $displayorder $row]
3675 set kidx [lsearch -exact [lindex $rowidlist $row] $kid]
3676 if {$kidx < 0} continue
3677 set nextrow [lindex $rowidlist [expr {$row + 1}]]
3679 set px [lsearch -exact $nextrow $p]
3680 if {$px < 0} continue
3681 if {($kidx < $x && $x < $px) || ($px < $x && $x < $kidx)} {
3682 if {[lsearch -exact $ccross $p] >= 0} continue
3683 if {$x == $px + ($kidx < $px? -1: 1)} {
3685 } elseif {[lsearch -exact $cross $p] < 0} {
3692 return [concat $ccross {{}} $cross]
3695 proc assigncolor {id} {
3696 global colormap colors nextcolor
3697 global commitrow parentlist children children curview
3699 if {[info exists colormap($id)]} return
3700 set ncolors [llength $colors]
3701 if {[info exists children($curview,$id)]} {
3702 set kids $children($curview,$id)
3706 if {[llength $kids] == 1} {
3707 set child [lindex $kids 0]
3708 if {[info exists colormap($child)]
3709 && [llength [lindex $parentlist $commitrow($curview,$child)]] == 1} {
3710 set colormap($id) $colormap($child)
3716 foreach x [findcrossings $id] {
3718 # delimiter between corner crossings and other crossings
3719 if {[llength $badcolors] >= $ncolors - 1} break
3720 set origbad $badcolors
3722 if {[info exists colormap($x)]
3723 && [lsearch -exact $badcolors $colormap($x)] < 0} {
3724 lappend badcolors $colormap($x)
3727 if {[llength $badcolors] >= $ncolors} {
3728 set badcolors $origbad
3730 set origbad $badcolors
3731 if {[llength $badcolors] < $ncolors - 1} {
3732 foreach child $kids {
3733 if {[info exists colormap($child)]
3734 && [lsearch -exact $badcolors $colormap($child)] < 0} {
3735 lappend badcolors $colormap($child)
3737 foreach p [lindex $parentlist $commitrow($curview,$child)] {
3738 if {[info exists colormap($p)]
3739 && [lsearch -exact $badcolors $colormap($p)] < 0} {
3740 lappend badcolors $colormap($p)
3744 if {[llength $badcolors] >= $ncolors} {
3745 set badcolors $origbad
3748 for {set i 0} {$i <= $ncolors} {incr i} {
3749 set c [lindex $colors $nextcolor]
3750 if {[incr nextcolor] >= $ncolors} {
3753 if {[lsearch -exact $badcolors $c]} break
3755 set colormap($id) $c
3758 proc bindline {t id} {
3761 $canv bind $t <Enter> "lineenter %x %y $id"
3762 $canv bind $t <Motion> "linemotion %x %y $id"
3763 $canv bind $t <Leave> "lineleave $id"
3764 $canv bind $t <Button-1> "lineclick %x %y $id 1"
3767 proc drawtags {id x xt y1} {
3768 global idtags idheads idotherrefs mainhead
3769 global linespc lthickness
3770 global canv mainfont commitrow rowtextx curview fgcolor bgcolor
3775 if {[info exists idtags($id)]} {
3776 set marks $idtags($id)
3777 set ntags [llength $marks]
3779 if {[info exists idheads($id)]} {
3780 set marks [concat $marks $idheads($id)]
3781 set nheads [llength $idheads($id)]
3783 if {[info exists idotherrefs($id)]} {
3784 set marks [concat $marks $idotherrefs($id)]
3790 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
3791 set yt [expr {$y1 - 0.5 * $linespc}]
3792 set yb [expr {$yt + $linespc - 1}]
3796 foreach tag $marks {
3798 if {$i >= $ntags && $i < $ntags + $nheads && $tag eq $mainhead} {
3799 set wid [font measure [concat $mainfont bold] $tag]
3801 set wid [font measure $mainfont $tag]
3805 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
3807 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
3808 -width $lthickness -fill black -tags tag.$id]
3810 foreach tag $marks x $xvals wid $wvals {
3811 set xl [expr {$x + $delta}]
3812 set xr [expr {$x + $delta + $wid + $lthickness}]
3814 if {[incr ntags -1] >= 0} {
3816 set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \
3817 $xr $yt $xr $yb $xl $yb $x [expr {$yb - $delta}] \
3818 -width 1 -outline black -fill yellow -tags tag.$id]
3819 $canv bind $t <1> [list showtag $tag 1]
3820 set rowtextx($commitrow($curview,$id)) [expr {$xr + $linespc}]
3822 # draw a head or other ref
3823 if {[incr nheads -1] >= 0} {
3825 if {$tag eq $mainhead} {
3831 set xl [expr {$xl - $delta/2}]
3832 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
3833 -width 1 -outline black -fill $col -tags tag.$id
3834 if {[regexp {^(remotes/.*/|remotes/)} $tag match remoteprefix]} {
3835 set rwid [font measure $mainfont $remoteprefix]
3836 set xi [expr {$x + 1}]
3837 set yti [expr {$yt + 1}]
3838 set xri [expr {$x + $rwid}]
3839 $canv create polygon $xi $yti $xri $yti $xri $yb $xi $yb \
3840 -width 0 -fill "#ffddaa" -tags tag.$id
3843 set t [$canv create text $xl $y1 -anchor w -text $tag -fill $fgcolor \
3844 -font $font -tags [list tag.$id text]]
3846 $canv bind $t <1> [list showtag $tag 1]
3847 } elseif {$nheads >= 0} {
3848 $canv bind $t <Button-3> [list headmenu %X %Y $id $tag]
3854 proc xcoord {i level ln} {
3855 global canvx0 xspc1 xspc2
3857 set x [expr {$canvx0 + $i * $xspc1($ln)}]
3858 if {$i > 0 && $i == $level} {
3859 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
3860 } elseif {$i > $level} {
3861 set x [expr {$x + $xspc2 - $xspc1($ln)}]
3866 proc show_status {msg} {
3867 global canv mainfont fgcolor
3870 $canv create text 3 3 -anchor nw -text $msg -font $mainfont \
3871 -tags text -fill $fgcolor
3874 # Insert a new commit as the child of the commit on row $row.
3875 # The new commit will be displayed on row $row and the commits
3876 # on that row and below will move down one row.
3877 proc insertrow {row newcmit} {
3878 global displayorder parentlist commitlisted children
3879 global commitrow curview rowidlist numcommits
3880 global rowrangelist rowlaidout rowoptim numcommits
3881 global selectedline rowchk commitidx
3883 if {$row >= $numcommits} {
3884 puts "oops, inserting new row $row but only have $numcommits rows"
3887 set p [lindex $displayorder $row]
3888 set displayorder [linsert $displayorder $row $newcmit]
3889 set parentlist [linsert $parentlist $row $p]
3890 set kids $children($curview,$p)
3891 lappend kids $newcmit
3892 set children($curview,$p) $kids
3893 set children($curview,$newcmit) {}
3894 set commitlisted [linsert $commitlisted $row 1]
3895 set l [llength $displayorder]
3896 for {set r $row} {$r < $l} {incr r} {
3897 set id [lindex $displayorder $r]
3898 set commitrow($curview,$id) $r
3900 incr commitidx($curview)
3902 set idlist [lindex $rowidlist $row]
3903 if {[llength $kids] == 1} {
3904 set col [lsearch -exact $idlist $p]
3905 lset idlist $col $newcmit
3907 set col [llength $idlist]
3908 lappend idlist $newcmit
3910 set rowidlist [linsert $rowidlist $row $idlist]
3912 set rowrangelist [linsert $rowrangelist $row {}]
3913 if {[llength $kids] > 1} {
3914 set rp1 [expr {$row + 1}]
3915 set ranges [lindex $rowrangelist $rp1]
3916 if {$ranges eq {}} {
3917 set ranges [list $newcmit $p]
3918 } elseif {[lindex $ranges end-1] eq $p} {
3919 lset ranges end-1 $newcmit
3921 lset rowrangelist $rp1 $ranges
3924 catch {unset rowchk}
3930 if {[info exists selectedline] && $selectedline >= $row} {
3936 # Remove a commit that was inserted with insertrow on row $row.
3937 proc removerow {row} {
3938 global displayorder parentlist commitlisted children
3939 global commitrow curview rowidlist numcommits
3940 global rowrangelist idrowranges rowlaidout rowoptim numcommits
3941 global linesegends selectedline rowchk commitidx
3943 if {$row >= $numcommits} {
3944 puts "oops, removing row $row but only have $numcommits rows"
3947 set rp1 [expr {$row + 1}]
3948 set id [lindex $displayorder $row]
3949 set p [lindex $parentlist $row]
3950 set displayorder [lreplace $displayorder $row $row]
3951 set parentlist [lreplace $parentlist $row $row]
3952 set commitlisted [lreplace $commitlisted $row $row]
3953 set kids $children($curview,$p)
3954 set i [lsearch -exact $kids $id]
3956 set kids [lreplace $kids $i $i]
3957 set children($curview,$p) $kids
3959 set l [llength $displayorder]
3960 for {set r $row} {$r < $l} {incr r} {
3961 set id [lindex $displayorder $r]
3962 set commitrow($curview,$id) $r
3964 incr commitidx($curview) -1
3966 set rowidlist [lreplace $rowidlist $row $row]
3968 set rowrangelist [lreplace $rowrangelist $row $row]
3969 if {[llength $kids] > 0} {
3970 set ranges [lindex $rowrangelist $row]
3971 if {[lindex $ranges end-1] eq $id} {
3972 set ranges [lreplace $ranges end-1 end]
3973 lset rowrangelist $row $ranges
3977 catch {unset rowchk}
3983 if {[info exists selectedline] && $selectedline > $row} {
3984 incr selectedline -1
3989 # Don't change the text pane cursor if it is currently the hand cursor,
3990 # showing that we are over a sha1 ID link.
3991 proc settextcursor {c} {
3992 global ctext curtextcursor
3994 if {[$ctext cget -cursor] == $curtextcursor} {
3995 $ctext config -cursor $c
3997 set curtextcursor $c
4000 proc nowbusy {what} {
4003 if {[array names isbusy] eq {}} {
4004 . config -cursor watch
4010 proc notbusy {what} {
4011 global isbusy maincursor textcursor
4013 catch {unset isbusy($what)}
4014 if {[array names isbusy] eq {}} {
4015 . config -cursor $maincursor
4016 settextcursor $textcursor
4020 proc findmatches {f} {
4021 global findtype findstring
4022 if {$findtype == "Regexp"} {
4023 set matches [regexp -indices -all -inline $findstring $f]
4026 if {$findtype == "IgnCase"} {
4027 set f [string tolower $f]
4028 set fs [string tolower $fs]
4032 set l [string length $fs]
4033 while {[set j [string first $fs $f $i]] >= 0} {
4034 lappend matches [list $j [expr {$j+$l-1}]]
4035 set i [expr {$j + $l}]
4041 proc dofind {{rev 0}} {
4042 global findstring findstartline findcurline selectedline numcommits
4045 cancel_next_highlight
4047 if {$findstring eq {} || $numcommits == 0} return
4048 if {![info exists selectedline]} {
4049 set findstartline [lindex [visiblerows] $rev]
4051 set findstartline $selectedline
4053 set findcurline $findstartline
4058 if {$findcurline == 0} {
4059 set findcurline $numcommits
4066 proc findnext {restart} {
4068 if {![info exists findcurline]} {
4082 if {![info exists findcurline]} {
4091 global commitdata commitinfo numcommits findstring findpattern findloc
4092 global findstartline findcurline displayorder
4094 set fldtypes {Headline Author Date Committer CDate Comments}
4095 set l [expr {$findcurline + 1}]
4096 if {$l >= $numcommits} {
4099 if {$l <= $findstartline} {
4100 set lim [expr {$findstartline + 1}]
4104 if {$lim - $l > 500} {
4105 set lim [expr {$l + 500}]
4108 for {} {$l < $lim} {incr l} {
4109 set id [lindex $displayorder $l]
4110 # shouldn't happen unless git log doesn't give all the commits...
4111 if {![info exists commitdata($id)]} continue
4112 if {![doesmatch $commitdata($id)]} continue
4113 if {![info exists commitinfo($id)]} {
4116 set info $commitinfo($id)
4117 foreach f $info ty $fldtypes {
4118 if {($findloc eq "All fields" || $findloc eq $ty) &&
4126 if {$l == $findstartline + 1} {
4132 set findcurline [expr {$l - 1}]
4136 proc findmorerev {} {
4137 global commitdata commitinfo numcommits findstring findpattern findloc
4138 global findstartline findcurline displayorder
4140 set fldtypes {Headline Author Date Committer CDate Comments}
4146 if {$l >= $findstartline} {
4147 set lim [expr {$findstartline - 1}]
4151 if {$l - $lim > 500} {
4152 set lim [expr {$l - 500}]
4155 for {} {$l > $lim} {incr l -1} {
4156 set id [lindex $displayorder $l]
4157 if {![doesmatch $commitdata($id)]} continue
4158 if {![info exists commitinfo($id)]} {
4161 set info $commitinfo($id)
4162 foreach f $info ty $fldtypes {
4163 if {($findloc eq "All fields" || $findloc eq $ty) &&
4177 set findcurline [expr {$l + 1}]
4181 proc findselectline {l} {
4182 global findloc commentend ctext findcurline markingmatches
4184 set markingmatches 1
4187 if {$findloc == "All fields" || $findloc == "Comments"} {
4188 # highlight the matches in the comments
4189 set f [$ctext get 1.0 $commentend]
4190 set matches [findmatches $f]
4191 foreach match $matches {
4192 set start [lindex $match 0]
4193 set end [expr {[lindex $match 1] + 1}]
4194 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
4200 # mark the bits of a headline or author that match a find string
4201 proc markmatches {canv l str tag matches font row} {
4204 set bbox [$canv bbox $tag]
4205 set x0 [lindex $bbox 0]
4206 set y0 [lindex $bbox 1]
4207 set y1 [lindex $bbox 3]
4208 foreach match $matches {
4209 set start [lindex $match 0]
4210 set end [lindex $match 1]
4211 if {$start > $end} continue
4212 set xoff [font measure $font [string range $str 0 [expr {$start-1}]]]
4213 set xlen [font measure $font [string range $str 0 [expr {$end}]]]
4214 set t [$canv create rect [expr {$x0+$xoff}] $y0 \
4215 [expr {$x0+$xlen+2}] $y1 \
4216 -outline {} -tags [list match$l matches] -fill yellow]
4218 if {[info exists selectedline] && $row == $selectedline} {
4219 $canv raise $t secsel
4224 proc unmarkmatches {} {
4225 global findids markingmatches findcurline
4227 allcanvs delete matches
4228 catch {unset findids}
4229 set markingmatches 0
4230 catch {unset findcurline}
4233 proc selcanvline {w x y} {
4234 global canv canvy0 ctext linespc
4236 set ymax [lindex [$canv cget -scrollregion] 3]
4237 if {$ymax == {}} return
4238 set yfrac [lindex [$canv yview] 0]
4239 set y [expr {$y + $yfrac * $ymax}]
4240 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
4245 if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return
4251 proc commit_descriptor {p} {
4253 if {![info exists commitinfo($p)]} {
4257 if {[llength $commitinfo($p)] > 1} {
4258 set l [lindex $commitinfo($p) 0]
4263 # append some text to the ctext widget, and make any SHA1 ID
4264 # that we know about be a clickable link.
4265 proc appendwithlinks {text tags} {
4266 global ctext commitrow linknum curview
4268 set start [$ctext index "end - 1c"]
4269 $ctext insert end $text $tags
4270 set links [regexp -indices -all -inline {[0-9a-f]{40}} $text]
4274 set linkid [string range $text $s $e]
4275 if {![info exists commitrow($curview,$linkid)]} continue
4277 $ctext tag add link "$start + $s c" "$start + $e c"
4278 $ctext tag add link$linknum "$start + $s c" "$start + $e c"
4279 $ctext tag bind link$linknum <1> \
4280 [list selectline $commitrow($curview,$linkid) 1]
4283 $ctext tag conf link -foreground blue -underline 1
4284 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
4285 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
4288 proc viewnextline {dir} {
4292 set ymax [lindex [$canv cget -scrollregion] 3]
4293 set wnow [$canv yview]
4294 set wtop [expr {[lindex $wnow 0] * $ymax}]
4295 set newtop [expr {$wtop + $dir * $linespc}]
4298 } elseif {$newtop > $ymax} {
4301 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
4304 # add a list of tag or branch names at position pos
4305 # returns the number of names inserted
4306 proc appendrefs {pos ids var} {
4307 global ctext commitrow linknum curview $var maxrefs
4309 if {[catch {$ctext index $pos}]} {
4312 $ctext conf -state normal
4313 $ctext delete $pos "$pos lineend"
4316 foreach tag [set $var\($id\)] {
4317 lappend tags [list $tag $id]
4320 if {[llength $tags] > $maxrefs} {
4321 $ctext insert $pos "many ([llength $tags])"
4323 set tags [lsort -index 0 -decreasing $tags]
4326 set id [lindex $ti 1]
4329 $ctext tag delete $lk
4330 $ctext insert $pos $sep
4331 $ctext insert $pos [lindex $ti 0] $lk
4332 if {[info exists commitrow($curview,$id)]} {
4333 $ctext tag conf $lk -foreground blue
4334 $ctext tag bind $lk <1> \
4335 [list selectline $commitrow($curview,$id) 1]
4336 $ctext tag conf $lk -underline 1
4337 $ctext tag bind $lk <Enter> { %W configure -cursor hand2 }
4338 $ctext tag bind $lk <Leave> \
4339 { %W configure -cursor $curtextcursor }
4344 $ctext conf -state disabled
4345 return [llength $tags]
4348 # called when we have finished computing the nearby tags
4349 proc dispneartags {delay} {
4350 global selectedline currentid showneartags tagphase
4352 if {![info exists selectedline] || !$showneartags} return
4353 after cancel dispnexttag
4355 after 200 dispnexttag
4358 after idle dispnexttag
4363 proc dispnexttag {} {
4364 global selectedline currentid showneartags tagphase ctext
4366 if {![info exists selectedline] || !$showneartags} return
4367 switch -- $tagphase {
4369 set dtags [desctags $currentid]
4371 appendrefs precedes $dtags idtags
4375 set atags [anctags $currentid]
4377 appendrefs follows $atags idtags
4381 set dheads [descheads $currentid]
4382 if {$dheads ne {}} {
4383 if {[appendrefs branch $dheads idheads] > 1
4384 && [$ctext get "branch -3c"] eq "h"} {
4385 # turn "Branch" into "Branches"
4386 $ctext conf -state normal
4387 $ctext insert "branch -2c" "es"
4388 $ctext conf -state disabled
4393 if {[incr tagphase] <= 2} {
4394 after idle dispnexttag
4398 proc selectline {l isnew} {
4399 global canv canv2 canv3 ctext commitinfo selectedline
4400 global displayorder linehtag linentag linedtag
4401 global canvy0 linespc parentlist children curview
4402 global currentid sha1entry
4403 global commentend idtags linknum
4404 global mergemax numcommits pending_select
4405 global cmitmode showneartags allcommits
4407 catch {unset pending_select}
4410 cancel_next_highlight
4411 if {$l < 0 || $l >= $numcommits} return
4412 set y [expr {$canvy0 + $l * $linespc}]
4413 set ymax [lindex [$canv cget -scrollregion] 3]
4414 set ytop [expr {$y - $linespc - 1}]
4415 set ybot [expr {$y + $linespc + 1}]
4416 set wnow [$canv yview]
4417 set wtop [expr {[lindex $wnow 0] * $ymax}]
4418 set wbot [expr {[lindex $wnow 1] * $ymax}]
4419 set wh [expr {$wbot - $wtop}]
4421 if {$ytop < $wtop} {
4422 if {$ybot < $wtop} {
4423 set newtop [expr {$y - $wh / 2.0}]
4426 if {$newtop > $wtop - $linespc} {
4427 set newtop [expr {$wtop - $linespc}]
4430 } elseif {$ybot > $wbot} {
4431 if {$ytop > $wbot} {
4432 set newtop [expr {$y - $wh / 2.0}]
4434 set newtop [expr {$ybot - $wh}]
4435 if {$newtop < $wtop + $linespc} {
4436 set newtop [expr {$wtop + $linespc}]
4440 if {$newtop != $wtop} {
4444 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
4448 if {![info exists linehtag($l)]} return
4450 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
4451 -tags secsel -fill [$canv cget -selectbackground]]
4453 $canv2 delete secsel
4454 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
4455 -tags secsel -fill [$canv2 cget -selectbackground]]
4457 $canv3 delete secsel
4458 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
4459 -tags secsel -fill [$canv3 cget -selectbackground]]
4463 addtohistory [list selectline $l 0]
4468 set id [lindex $displayorder $l]
4470 $sha1entry delete 0 end
4471 $sha1entry insert 0 $id
4472 $sha1entry selection from 0
4473 $sha1entry selection to end
4476 $ctext conf -state normal
4479 set info $commitinfo($id)
4480 set date [formatdate [lindex $info 2]]
4481 $ctext insert end "Author: [lindex $info 1] $date\n"
4482 set date [formatdate [lindex $info 4]]
4483 $ctext insert end "Committer: [lindex $info 3] $date\n"
4484 if {[info exists idtags($id)]} {
4485 $ctext insert end "Tags:"
4486 foreach tag $idtags($id) {
4487 $ctext insert end " $tag"
4489 $ctext insert end "\n"
4493 set olds [lindex $parentlist $l]
4494 if {[llength $olds] > 1} {
4497 if {$np >= $mergemax} {
4502 $ctext insert end "Parent: " $tag
4503 appendwithlinks [commit_descriptor $p] {}
4508 append headers "Parent: [commit_descriptor $p]"
4512 foreach c $children($curview,$id) {
4513 append headers "Child: [commit_descriptor $c]"
4516 # make anything that looks like a SHA1 ID be a clickable link
4517 appendwithlinks $headers {}
4518 if {$showneartags} {
4519 if {![info exists allcommits]} {
4522 $ctext insert end "Branch: "
4523 $ctext mark set branch "end -1c"
4524 $ctext mark gravity branch left
4525 $ctext insert end "\nFollows: "
4526 $ctext mark set follows "end -1c"
4527 $ctext mark gravity follows left
4528 $ctext insert end "\nPrecedes: "
4529 $ctext mark set precedes "end -1c"
4530 $ctext mark gravity precedes left
4531 $ctext insert end "\n"
4534 $ctext insert end "\n"
4535 set comment [lindex $info 5]
4536 if {[string first "\r" $comment] >= 0} {
4537 set comment [string map {"\r" "\n "} $comment]
4539 appendwithlinks $comment {comment}
4541 $ctext tag remove found 1.0 end
4542 $ctext conf -state disabled
4543 set commentend [$ctext index "end - 1c"]
4545 init_flist "Comments"
4546 if {$cmitmode eq "tree"} {
4548 } elseif {[llength $olds] <= 1} {
4555 proc selfirstline {} {
4560 proc sellastline {} {
4563 set l [expr {$numcommits - 1}]
4567 proc selnextline {dir} {
4569 if {![info exists selectedline]} return
4570 set l [expr {$selectedline + $dir}]
4575 proc selnextpage {dir} {
4576 global canv linespc selectedline numcommits
4578 set lpp [expr {([winfo height $canv] - 2) / $linespc}]
4582 allcanvs yview scroll [expr {$dir * $lpp}] units
4584 if {![info exists selectedline]} return
4585 set l [expr {$selectedline + $dir * $lpp}]
4588 } elseif {$l >= $numcommits} {
4589 set l [expr $numcommits - 1]
4595 proc unselectline {} {
4596 global selectedline currentid
4598 catch {unset selectedline}
4599 catch {unset currentid}
4600 allcanvs delete secsel
4602 cancel_next_highlight
4605 proc reselectline {} {
4608 if {[info exists selectedline]} {
4609 selectline $selectedline 0
4613 proc addtohistory {cmd} {
4614 global history historyindex curview
4616 set elt [list $curview $cmd]
4617 if {$historyindex > 0
4618 && [lindex $history [expr {$historyindex - 1}]] == $elt} {
4622 if {$historyindex < [llength $history]} {
4623 set history [lreplace $history $historyindex end $elt]
4625 lappend history $elt
4628 if {$historyindex > 1} {
4629 .tf.bar.leftbut conf -state normal
4631 .tf.bar.leftbut conf -state disabled
4633 .tf.bar.rightbut conf -state disabled
4639 set view [lindex $elt 0]
4640 set cmd [lindex $elt 1]
4641 if {$curview != $view} {
4648 global history historyindex
4650 if {$historyindex > 1} {
4651 incr historyindex -1
4652 godo [lindex $history [expr {$historyindex - 1}]]
4653 .tf.bar.rightbut conf -state normal
4655 if {$historyindex <= 1} {
4656 .tf.bar.leftbut conf -state disabled
4661 global history historyindex
4663 if {$historyindex < [llength $history]} {
4664 set cmd [lindex $history $historyindex]
4667 .tf.bar.leftbut conf -state normal
4669 if {$historyindex >= [llength $history]} {
4670 .tf.bar.rightbut conf -state disabled
4675 global treefilelist treeidlist diffids diffmergeid treepending
4676 global nullid nullid2
4679 catch {unset diffmergeid}
4680 if {![info exists treefilelist($id)]} {
4681 if {![info exists treepending]} {
4682 if {$id eq $nullid} {
4683 set cmd [list | git ls-files]
4684 } elseif {$id eq $nullid2} {
4685 set cmd [list | git ls-files --stage -t]
4687 set cmd [list | git ls-tree -r $id]
4689 if {[catch {set gtf [open $cmd r]}]} {
4693 set treefilelist($id) {}
4694 set treeidlist($id) {}
4695 fconfigure $gtf -blocking 0
4696 filerun $gtf [list gettreeline $gtf $id]
4703 proc gettreeline {gtf id} {
4704 global treefilelist treeidlist treepending cmitmode diffids nullid nullid2
4707 while {[incr nl] <= 1000 && [gets $gtf line] >= 0} {
4708 if {$diffids eq $nullid} {
4711 if {$diffids ne $nullid2 && [lindex $line 1] ne "blob"} continue
4712 set i [string first "\t" $line]
4713 if {$i < 0} continue
4714 set sha1 [lindex $line 2]
4715 set fname [string range $line [expr {$i+1}] end]
4716 if {[string index $fname 0] eq "\""} {
4717 set fname [lindex $fname 0]
4719 lappend treeidlist($id) $sha1
4721 lappend treefilelist($id) $fname
4724 return [expr {$nl >= 1000? 2: 1}]
4728 if {$cmitmode ne "tree"} {
4729 if {![info exists diffmergeid]} {
4730 gettreediffs $diffids
4732 } elseif {$id ne $diffids} {
4741 global treefilelist treeidlist diffids nullid nullid2
4742 global ctext commentend
4744 set i [lsearch -exact $treefilelist($diffids) $f]
4746 puts "oops, $f not in list for id $diffids"
4749 if {$diffids eq $nullid} {
4750 if {[catch {set bf [open $f r]} err]} {
4751 puts "oops, can't read $f: $err"
4755 set blob [lindex $treeidlist($diffids) $i]
4756 if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
4757 puts "oops, error reading blob $blob: $err"
4761 fconfigure $bf -blocking 0
4762 filerun $bf [list getblobline $bf $diffids]
4763 $ctext config -state normal
4764 clear_ctext $commentend
4765 $ctext insert end "\n"
4766 $ctext insert end "$f\n" filesep
4767 $ctext config -state disabled
4768 $ctext yview $commentend
4771 proc getblobline {bf id} {
4772 global diffids cmitmode ctext
4774 if {$id ne $diffids || $cmitmode ne "tree"} {
4778 $ctext config -state normal
4780 while {[incr nl] <= 1000 && [gets $bf line] >= 0} {
4781 $ctext insert end "$line\n"
4784 # delete last newline
4785 $ctext delete "end - 2c" "end - 1c"
4789 $ctext config -state disabled
4790 return [expr {$nl >= 1000? 2: 1}]
4793 proc mergediff {id l} {
4794 global diffmergeid diffopts mdifffd
4800 # this doesn't seem to actually affect anything...
4801 set env(GIT_DIFF_OPTS) $diffopts
4802 set cmd [concat | git diff-tree --no-commit-id --cc $id]
4803 if {[catch {set mdf [open $cmd r]} err]} {
4804 error_popup "Error getting merge diffs: $err"
4807 fconfigure $mdf -blocking 0
4808 set mdifffd($id) $mdf
4809 set np [llength [lindex $parentlist $l]]
4810 filerun $mdf [list getmergediffline $mdf $id $np]
4813 proc getmergediffline {mdf id np} {
4814 global diffmergeid ctext cflist mergemax
4815 global difffilestart mdifffd
4817 $ctext conf -state normal
4819 while {[incr nr] <= 1000 && [gets $mdf line] >= 0} {
4820 if {![info exists diffmergeid] || $id != $diffmergeid
4821 || $mdf != $mdifffd($id)} {
4825 if {[regexp {^diff --cc (.*)} $line match fname]} {
4826 # start of a new file
4827 $ctext insert end "\n"
4828 set here [$ctext index "end - 1c"]
4829 lappend difffilestart $here
4830 add_flist [list $fname]
4831 set l [expr {(78 - [string length $fname]) / 2}]
4832 set pad [string range "----------------------------------------" 1 $l]
4833 $ctext insert end "$pad $fname $pad\n" filesep
4834 } elseif {[regexp {^@@} $line]} {
4835 $ctext insert end "$line\n" hunksep
4836 } elseif {[regexp {^[0-9a-f]{40}$} $line] || [regexp {^index} $line]} {
4839 # parse the prefix - one ' ', '-' or '+' for each parent
4844 for {set j 0} {$j < $np} {incr j} {
4845 set c [string range $line $j $j]
4848 } elseif {$c == "-"} {
4850 } elseif {$c == "+"} {
4859 if {!$isbad && $minuses ne {} && $pluses eq {}} {
4860 # line doesn't appear in result, parents in $minuses have the line
4861 set num [lindex $minuses 0]
4862 } elseif {!$isbad && $pluses ne {} && $minuses eq {}} {
4863 # line appears in result, parents in $pluses don't have the line
4864 lappend tags mresult
4865 set num [lindex $spaces 0]
4868 if {$num >= $mergemax} {
4873 $ctext insert end "$line\n" $tags
4876 $ctext conf -state disabled
4881 return [expr {$nr >= 1000? 2: 1}]
4884 proc startdiff {ids} {
4885 global treediffs diffids treepending diffmergeid nullid nullid2
4888 catch {unset diffmergeid}
4889 if {![info exists treediffs($ids)] ||
4890 [lsearch -exact $ids $nullid] >= 0 ||
4891 [lsearch -exact $ids $nullid2] >= 0} {
4892 if {![info exists treepending]} {
4900 proc addtocflist {ids} {
4901 global treediffs cflist
4902 add_flist $treediffs($ids)
4906 proc diffcmd {ids flags} {
4907 global nullid nullid2
4909 set i [lsearch -exact $ids $nullid]
4910 set j [lsearch -exact $ids $nullid2]
4912 if {[llength $ids] > 1 && $j < 0} {
4913 # comparing working directory with some specific revision
4914 set cmd [concat | git diff-index $flags]
4916 lappend cmd -R [lindex $ids 1]
4918 lappend cmd [lindex $ids 0]
4921 # comparing working directory with index
4922 set cmd [concat | git diff-files $flags]
4927 } elseif {$j >= 0} {
4928 set cmd [concat | git diff-index --cached $flags]
4929 if {[llength $ids] > 1} {
4930 # comparing index with specific revision
4932 lappend cmd -R [lindex $ids 1]
4934 lappend cmd [lindex $ids 0]
4937 # comparing index with HEAD
4941 set cmd [concat | git diff-tree -r $flags $ids]
4946 proc gettreediffs {ids} {
4947 global treediff treepending
4949 set treepending $ids
4951 if {[catch {set gdtf [open [diffcmd $ids {--no-commit-id}] r]}]} return
4952 fconfigure $gdtf -blocking 0
4953 filerun $gdtf [list gettreediffline $gdtf $ids]
4956 proc gettreediffline {gdtf ids} {
4957 global treediff treediffs treepending diffids diffmergeid
4961 while {[incr nr] <= 1000 && [gets $gdtf line] >= 0} {
4962 set i [string first "\t" $line]
4964 set file [string range $line [expr {$i+1}] end]
4965 if {[string index $file 0] eq "\""} {
4966 set file [lindex $file 0]
4968 lappend treediff $file
4972 return [expr {$nr >= 1000? 2: 1}]
4975 set treediffs($ids) $treediff
4977 if {$cmitmode eq "tree"} {
4979 } elseif {$ids != $diffids} {
4980 if {![info exists diffmergeid]} {
4981 gettreediffs $diffids
4989 proc getblobdiffs {ids} {
4990 global diffopts blobdifffd diffids env
4991 global diffinhdr treediffs
4993 set env(GIT_DIFF_OPTS) $diffopts
4994 if {[catch {set bdf [open [diffcmd $ids {-p -C --no-commit-id}] r]} err]} {
4995 puts "error getting diffs: $err"
4999 fconfigure $bdf -blocking 0
5000 set blobdifffd($ids) $bdf
5001 filerun $bdf [list getblobdiffline $bdf $diffids]
5004 proc setinlist {var i val} {
5007 while {[llength [set $var]] < $i} {
5010 if {[llength [set $var]] == $i} {
5017 proc makediffhdr {fname ids} {
5018 global ctext curdiffstart treediffs
5020 set i [lsearch -exact $treediffs($ids) $fname]
5022 setinlist difffilestart $i $curdiffstart
5024 set l [expr {(78 - [string length $fname]) / 2}]
5025 set pad [string range "----------------------------------------" 1 $l]
5026 $ctext insert $curdiffstart "$pad $fname $pad" filesep
5029 proc getblobdiffline {bdf ids} {
5030 global diffids blobdifffd ctext curdiffstart
5031 global diffnexthead diffnextnote difffilestart
5032 global diffinhdr treediffs
5035 $ctext conf -state normal
5036 while {[incr nr] <= 1000 && [gets $bdf line] >= 0} {
5037 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
5041 if {![string compare -length 11 "diff --git " $line]} {
5042 # trim off "diff --git "
5043 set line [string range $line 11 end]
5045 # start of a new file
5046 $ctext insert end "\n"
5047 set curdiffstart [$ctext index "end - 1c"]
5048 $ctext insert end "\n" filesep
5049 # If the name hasn't changed the length will be odd,
5050 # the middle char will be a space, and the two bits either
5051 # side will be a/name and b/name, or "a/name" and "b/name".
5052 # If the name has changed we'll get "rename from" and
5053 # "rename to" lines following this, and we'll use them
5054 # to get the filenames.
5055 # This complexity is necessary because spaces in the filename(s)
5056 # don't get escaped.
5057 set l [string length $line]
5058 set i [expr {$l / 2}]
5059 if {!(($l & 1) && [string index $line $i] eq " " &&
5060 [string range $line 2 [expr {$i - 1}]] eq \
5061 [string range $line [expr {$i + 3}] end])} {
5064 # unescape if quoted and chop off the a/ from the front
5065 if {[string index $line 0] eq "\""} {
5066 set fname [string range [lindex $line 0] 2 end]
5068 set fname [string range $line 2 [expr {$i - 1}]]
5070 makediffhdr $fname $ids
5072 } elseif {[regexp {^@@ -([0-9]+)(,[0-9]+)? \+([0-9]+)(,[0-9]+)? @@(.*)} \
5073 $line match f1l f1c f2l f2c rest]} {
5074 $ctext insert end "$line\n" hunksep
5077 } elseif {$diffinhdr} {
5078 if {![string compare -length 12 "rename from " $line]} {
5079 set fname [string range $line 12 end]
5080 if {[string index $fname 0] eq "\""} {
5081 set fname [lindex $fname 0]
5083 set i [lsearch -exact $treediffs($ids) $fname]
5085 setinlist difffilestart $i $curdiffstart
5087 } elseif {![string compare -length 10 $line "rename to "]} {
5088 set fname [string range $line 10 end]
5089 if {[string index $fname 0] eq "\""} {
5090 set fname [lindex $fname 0]
5092 makediffhdr $fname $ids
5093 } elseif {[string compare -length 3 $line "---"] == 0} {
5096 } elseif {[string compare -length 3 $line "+++"] == 0} {
5100 $ctext insert end "$line\n" filesep
5103 set x [string range $line 0 0]
5104 if {$x == "-" || $x == "+"} {
5105 set tag [expr {$x == "+"}]
5106 $ctext insert end "$line\n" d$tag
5107 } elseif {$x == " "} {
5108 $ctext insert end "$line\n"
5110 # "\ No newline at end of file",
5111 # or something else we don't recognize
5112 $ctext insert end "$line\n" hunksep
5116 $ctext conf -state disabled
5121 return [expr {$nr >= 1000? 2: 1}]
5124 proc changediffdisp {} {
5125 global ctext diffelide
5127 $ctext tag conf d0 -elide [lindex $diffelide 0]
5128 $ctext tag conf d1 -elide [lindex $diffelide 1]
5132 global difffilestart ctext
5133 set prev [lindex $difffilestart 0]
5134 set here [$ctext index @0,0]
5135 foreach loc $difffilestart {
5136 if {[$ctext compare $loc >= $here]} {
5146 global difffilestart ctext
5147 set here [$ctext index @0,0]
5148 foreach loc $difffilestart {
5149 if {[$ctext compare $loc > $here]} {
5156 proc clear_ctext {{first 1.0}} {
5157 global ctext smarktop smarkbot
5159 set l [lindex [split $first .] 0]
5160 if {![info exists smarktop] || [$ctext compare $first < $smarktop.0]} {
5163 if {![info exists smarkbot] || [$ctext compare $first < $smarkbot.0]} {
5166 $ctext delete $first end
5169 proc incrsearch {name ix op} {
5170 global ctext searchstring searchdirn
5172 $ctext tag remove found 1.0 end
5173 if {[catch {$ctext index anchor}]} {
5174 # no anchor set, use start of selection, or of visible area
5175 set sel [$ctext tag ranges sel]
5177 $ctext mark set anchor [lindex $sel 0]
5178 } elseif {$searchdirn eq "-forwards"} {
5179 $ctext mark set anchor @0,0
5181 $ctext mark set anchor @0,[winfo height $ctext]
5184 if {$searchstring ne {}} {
5185 set here [$ctext search $searchdirn -- $searchstring anchor]
5194 global sstring ctext searchstring searchdirn
5197 $sstring icursor end
5198 set searchdirn -forwards
5199 if {$searchstring ne {}} {
5200 set sel [$ctext tag ranges sel]
5202 set start "[lindex $sel 0] + 1c"
5203 } elseif {[catch {set start [$ctext index anchor]}]} {
5206 set match [$ctext search -count mlen -- $searchstring $start]
5207 $ctext tag remove sel 1.0 end
5213 set mend "$match + $mlen c"
5214 $ctext tag add sel $match $mend
5215 $ctext mark unset anchor
5219 proc dosearchback {} {
5220 global sstring ctext searchstring searchdirn
5223 $sstring icursor end
5224 set searchdirn -backwards
5225 if {$searchstring ne {}} {
5226 set sel [$ctext tag ranges sel]
5228 set start [lindex $sel 0]
5229 } elseif {[catch {set start [$ctext index anchor]}]} {
5230 set start @0,[winfo height $ctext]
5232 set match [$ctext search -backwards -count ml -- $searchstring $start]
5233 $ctext tag remove sel 1.0 end
5239 set mend "$match + $ml c"
5240 $ctext tag add sel $match $mend
5241 $ctext mark unset anchor
5245 proc searchmark {first last} {
5246 global ctext searchstring
5250 set match [$ctext search -count mlen -- $searchstring $mend $last.end]
5251 if {$match eq {}} break
5252 set mend "$match + $mlen c"
5253 $ctext tag add found $match $mend
5257 proc searchmarkvisible {doall} {
5258 global ctext smarktop smarkbot
5260 set topline [lindex [split [$ctext index @0,0] .] 0]
5261 set botline [lindex [split [$ctext index @0,[winfo height $ctext]] .] 0]
5262 if {$doall || $botline < $smarktop || $topline > $smarkbot} {
5263 # no overlap with previous
5264 searchmark $topline $botline
5265 set smarktop $topline
5266 set smarkbot $botline
5268 if {$topline < $smarktop} {
5269 searchmark $topline [expr {$smarktop-1}]
5270 set smarktop $topline
5272 if {$botline > $smarkbot} {
5273 searchmark [expr {$smarkbot+1}] $botline
5274 set smarkbot $botline
5279 proc scrolltext {f0 f1} {
5282 .bleft.sb set $f0 $f1
5283 if {$searchstring ne {}} {
5289 global linespc charspc canvx0 canvy0 mainfont
5290 global xspc1 xspc2 lthickness
5292 set linespc [font metrics $mainfont -linespace]
5293 set charspc [font measure $mainfont "m"]
5294 set canvy0 [expr {int(3 + 0.5 * $linespc)}]
5295 set canvx0 [expr {int(3 + 0.5 * $linespc)}]
5296 set lthickness [expr {int($linespc / 9) + 1}]
5297 set xspc1(0) $linespc
5305 set ymax [lindex [$canv cget -scrollregion] 3]
5306 if {$ymax eq {} || $ymax == 0} return
5307 set span [$canv yview]
5310 allcanvs yview moveto [lindex $span 0]
5312 if {[info exists selectedline]} {
5313 selectline $selectedline 0
5314 allcanvs yview moveto [lindex $span 0]
5318 proc incrfont {inc} {
5319 global mainfont textfont ctext canv phase cflist
5320 global charspc tabstop
5321 global stopped entries
5323 set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
5324 set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
5326 $ctext conf -font $textfont -tabs "[expr {$tabstop * $charspc}]"
5327 $cflist conf -font $textfont
5328 $ctext tag conf filesep -font [concat $textfont bold]
5329 foreach e $entries {
5330 $e conf -font $mainfont
5332 if {$phase eq "getcommits"} {
5333 $canv itemconf textitems -font $mainfont
5339 global sha1entry sha1string
5340 if {[string length $sha1string] == 40} {
5341 $sha1entry delete 0 end
5345 proc sha1change {n1 n2 op} {
5346 global sha1string currentid sha1but
5347 if {$sha1string == {}
5348 || ([info exists currentid] && $sha1string == $currentid)} {
5353 if {[$sha1but cget -state] == $state} return
5354 if {$state == "normal"} {
5355 $sha1but conf -state normal -relief raised -text "Goto: "
5357 $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
5361 proc gotocommit {} {
5362 global sha1string currentid commitrow tagids headids
5363 global displayorder numcommits curview
5365 if {$sha1string == {}
5366 || ([info exists currentid] && $sha1string == $currentid)} return
5367 if {[info exists tagids($sha1string)]} {
5368 set id $tagids($sha1string)
5369 } elseif {[info exists headids($sha1string)]} {
5370 set id $headids($sha1string)
5372 set id [string tolower $sha1string]
5373 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
5375 foreach i $displayorder {
5376 if {[string match $id* $i]} {
5380 if {$matches ne {}} {
5381 if {[llength $matches] > 1} {
5382 error_popup "Short SHA1 id $id is ambiguous"
5385 set id [lindex $matches 0]
5389 if {[info exists commitrow($curview,$id)]} {
5390 selectline $commitrow($curview,$id) 1
5393 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
5398 error_popup "$type $sha1string is not known"
5401 proc lineenter {x y id} {
5402 global hoverx hovery hoverid hovertimer
5403 global commitinfo canv
5405 if {![info exists commitinfo($id)] && ![getcommit $id]} return
5409 if {[info exists hovertimer]} {
5410 after cancel $hovertimer
5412 set hovertimer [after 500 linehover]
5416 proc linemotion {x y id} {
5417 global hoverx hovery hoverid hovertimer
5419 if {[info exists hoverid] && $id == $hoverid} {
5422 if {[info exists hovertimer]} {
5423 after cancel $hovertimer
5425 set hovertimer [after 500 linehover]
5429 proc lineleave {id} {
5430 global hoverid hovertimer canv
5432 if {[info exists hoverid] && $id == $hoverid} {
5434 if {[info exists hovertimer]} {
5435 after cancel $hovertimer
5443 global hoverx hovery hoverid hovertimer
5444 global canv linespc lthickness
5445 global commitinfo mainfont
5447 set text [lindex $commitinfo($hoverid) 0]
5448 set ymax [lindex [$canv cget -scrollregion] 3]
5449 if {$ymax == {}} return
5450 set yfrac [lindex [$canv yview] 0]
5451 set x [expr {$hoverx + 2 * $linespc}]
5452 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
5453 set x0 [expr {$x - 2 * $lthickness}]
5454 set y0 [expr {$y - 2 * $lthickness}]
5455 set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
5456 set y1 [expr {$y + $linespc + 2 * $lthickness}]
5457 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
5458 -fill \#ffff80 -outline black -width 1 -tags hover]
5460 set t [$canv create text $x $y -anchor nw -text $text -tags hover \
5465 proc clickisonarrow {id y} {
5468 set ranges [rowranges $id]
5469 set thresh [expr {2 * $lthickness + 6}]
5470 set n [expr {[llength $ranges] - 1}]
5471 for {set i 1} {$i < $n} {incr i} {
5472 set row [lindex $ranges $i]
5473 if {abs([yc $row] - $y) < $thresh} {
5480 proc arrowjump {id n y} {
5483 # 1 <-> 2, 3 <-> 4, etc...
5484 set n [expr {(($n - 1) ^ 1) + 1}]
5485 set row [lindex [rowranges $id] $n]
5487 set ymax [lindex [$canv cget -scrollregion] 3]
5488 if {$ymax eq {} || $ymax <= 0} return
5489 set view [$canv yview]
5490 set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
5491 set yfrac [expr {$yt / $ymax - $yspan / 2}]
5495 allcanvs yview moveto $yfrac
5498 proc lineclick {x y id isnew} {
5499 global ctext commitinfo children canv thickerline curview
5501 if {![info exists commitinfo($id)] && ![getcommit $id]} return
5506 # draw this line thicker than normal
5510 set ymax [lindex [$canv cget -scrollregion] 3]
5511 if {$ymax eq {}} return
5512 set yfrac [lindex [$canv yview] 0]
5513 set y [expr {$y + $yfrac * $ymax}]
5515 set dirn [clickisonarrow $id $y]
5517 arrowjump $id $dirn $y
5522 addtohistory [list lineclick $x $y $id 0]
5524 # fill the details pane with info about this line
5525 $ctext conf -state normal
5527 $ctext tag conf link -foreground blue -underline 1
5528 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
5529 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
5530 $ctext insert end "Parent:\t"
5531 $ctext insert end $id [list link link0]
5532 $ctext tag bind link0 <1> [list selbyid $id]
5533 set info $commitinfo($id)
5534 $ctext insert end "\n\t[lindex $info 0]\n"
5535 $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
5536 set date [formatdate [lindex $info 2]]
5537 $ctext insert end "\tDate:\t$date\n"
5538 set kids $children($curview,$id)
5540 $ctext insert end "\nChildren:"
5542 foreach child $kids {
5544 if {![info exists commitinfo($child)] && ![getcommit $child]} continue
5545 set info $commitinfo($child)
5546 $ctext insert end "\n\t"
5547 $ctext insert end $child [list link link$i]
5548 $ctext tag bind link$i <1> [list selbyid $child]
5549 $ctext insert end "\n\t[lindex $info 0]"
5550 $ctext insert end "\n\tAuthor:\t[lindex $info 1]"
5551 set date [formatdate [lindex $info 2]]
5552 $ctext insert end "\n\tDate:\t$date\n"
5555 $ctext conf -state disabled
5559 proc normalline {} {
5561 if {[info exists thickerline]} {
5569 global commitrow curview
5570 if {[info exists commitrow($curview,$id)]} {
5571 selectline $commitrow($curview,$id) 1
5577 if {![info exists startmstime]} {
5578 set startmstime [clock clicks -milliseconds]
5580 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
5583 proc rowmenu {x y id} {
5584 global rowctxmenu commitrow selectedline rowmenuid curview
5585 global nullid nullid2 fakerowmenu mainhead
5588 if {![info exists selectedline]
5589 || $commitrow($curview,$id) eq $selectedline} {
5594 if {$id ne $nullid && $id ne $nullid2} {
5595 set menu $rowctxmenu
5596 $menu entryconfigure 7 -label "Reset $mainhead branch to here"
5598 set menu $fakerowmenu
5600 $menu entryconfigure "Diff this*" -state $state
5601 $menu entryconfigure "Diff selected*" -state $state
5602 $menu entryconfigure "Make patch" -state $state
5603 tk_popup $menu $x $y
5606 proc diffvssel {dirn} {
5607 global rowmenuid selectedline displayorder
5609 if {![info exists selectedline]} return
5611 set oldid [lindex $displayorder $selectedline]
5612 set newid $rowmenuid
5614 set oldid $rowmenuid
5615 set newid [lindex $displayorder $selectedline]
5617 addtohistory [list doseldiff $oldid $newid]
5618 doseldiff $oldid $newid
5621 proc doseldiff {oldid newid} {
5625 $ctext conf -state normal
5628 $ctext insert end "From "
5629 $ctext tag conf link -foreground blue -underline 1
5630 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
5631 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
5632 $ctext tag bind link0 <1> [list selbyid $oldid]
5633 $ctext insert end $oldid [list link link0]
5634 $ctext insert end "\n "
5635 $ctext insert end [lindex $commitinfo($oldid) 0]
5636 $ctext insert end "\n\nTo "
5637 $ctext tag bind link1 <1> [list selbyid $newid]
5638 $ctext insert end $newid [list link link1]
5639 $ctext insert end "\n "
5640 $ctext insert end [lindex $commitinfo($newid) 0]
5641 $ctext insert end "\n"
5642 $ctext conf -state disabled
5643 $ctext tag remove found 1.0 end
5644 startdiff [list $oldid $newid]
5648 global rowmenuid currentid commitinfo patchtop patchnum
5650 if {![info exists currentid]} return
5651 set oldid $currentid
5652 set oldhead [lindex $commitinfo($oldid) 0]
5653 set newid $rowmenuid
5654 set newhead [lindex $commitinfo($newid) 0]
5657 catch {destroy $top}
5659 label $top.title -text "Generate patch"
5660 grid $top.title - -pady 10
5661 label $top.from -text "From:"
5662 entry $top.fromsha1 -width 40 -relief flat
5663 $top.fromsha1 insert 0 $oldid
5664 $top.fromsha1 conf -state readonly
5665 grid $top.from $top.fromsha1 -sticky w
5666 entry $top.fromhead -width 60 -relief flat
5667 $top.fromhead insert 0 $oldhead
5668 $top.fromhead conf -state readonly
5669 grid x $top.fromhead -sticky w
5670 label $top.to -text "To:"
5671 entry $top.tosha1 -width 40 -relief flat
5672 $top.tosha1 insert 0 $newid
5673 $top.tosha1 conf -state readonly
5674 grid $top.to $top.tosha1 -sticky w
5675 entry $top.tohead -width 60 -relief flat
5676 $top.tohead insert 0 $newhead
5677 $top.tohead conf -state readonly
5678 grid x $top.tohead -sticky w
5679 button $top.rev -text "Reverse" -command mkpatchrev -padx 5
5680 grid $top.rev x -pady 10
5681 label $top.flab -text "Output file:"
5682 entry $top.fname -width 60
5683 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
5685 grid $top.flab $top.fname -sticky w
5687 button $top.buts.gen -text "Generate" -command mkpatchgo
5688 button $top.buts.can -text "Cancel" -command mkpatchcan
5689 grid $top.buts.gen $top.buts.can
5690 grid columnconfigure $top.buts 0 -weight 1 -uniform a
5691 grid columnconfigure $top.buts 1 -weight 1 -uniform a
5692 grid $top.buts - -pady 10 -sticky ew
5696 proc mkpatchrev {} {
5699 set oldid [$patchtop.fromsha1 get]
5700 set oldhead [$patchtop.fromhead get]
5701 set newid [$patchtop.tosha1 get]
5702 set newhead [$patchtop.tohead get]
5703 foreach e [list fromsha1 fromhead tosha1 tohead] \
5704 v [list $newid $newhead $oldid $oldhead] {
5705 $patchtop.$e conf -state normal
5706 $patchtop.$e delete 0 end
5707 $patchtop.$e insert 0 $v
5708 $patchtop.$e conf -state readonly
5713 global patchtop nullid nullid2
5715 set oldid [$patchtop.fromsha1 get]
5716 set newid [$patchtop.tosha1 get]
5717 set fname [$patchtop.fname get]
5718 set cmd [diffcmd [list $oldid $newid] -p]
5719 lappend cmd >$fname &
5720 if {[catch {eval exec $cmd} err]} {
5721 error_popup "Error creating patch: $err"
5723 catch {destroy $patchtop}
5727 proc mkpatchcan {} {
5730 catch {destroy $patchtop}
5735 global rowmenuid mktagtop commitinfo
5739 catch {destroy $top}
5741 label $top.title -text "Create tag"
5742 grid $top.title - -pady 10
5743 label $top.id -text "ID:"
5744 entry $top.sha1 -width 40 -relief flat
5745 $top.sha1 insert 0 $rowmenuid
5746 $top.sha1 conf -state readonly
5747 grid $top.id $top.sha1 -sticky w
5748 entry $top.head -width 60 -relief flat
5749 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
5750 $top.head conf -state readonly
5751 grid x $top.head -sticky w
5752 label $top.tlab -text "Tag name:"
5753 entry $top.tag -width 60
5754 grid $top.tlab $top.tag -sticky w
5756 button $top.buts.gen -text "Create" -command mktaggo
5757 button $top.buts.can -text "Cancel" -command mktagcan
5758 grid $top.buts.gen $top.buts.can
5759 grid columnconfigure $top.buts 0 -weight 1 -uniform a
5760 grid columnconfigure $top.buts 1 -weight 1 -uniform a
5761 grid $top.buts - -pady 10 -sticky ew
5766 global mktagtop env tagids idtags
5768 set id [$mktagtop.sha1 get]
5769 set tag [$mktagtop.tag get]
5771 error_popup "No tag name specified"
5774 if {[info exists tagids($tag)]} {
5775 error_popup "Tag \"$tag\" already exists"
5780 set fname [file join $dir "refs/tags" $tag]
5781 set f [open $fname w]
5785 error_popup "Error creating tag: $err"
5789 set tagids($tag) $id
5790 lappend idtags($id) $tag
5795 proc redrawtags {id} {
5796 global canv linehtag commitrow idpos selectedline curview
5797 global mainfont canvxmax iddrawn
5799 if {![info exists commitrow($curview,$id)]} return
5800 if {![info exists iddrawn($id)]} return
5801 drawcommits $commitrow($curview,$id)
5802 $canv delete tag.$id
5803 set xt [eval drawtags $id $idpos($id)]
5804 $canv coords $linehtag($commitrow($curview,$id)) $xt [lindex $idpos($id) 2]
5805 set text [$canv itemcget $linehtag($commitrow($curview,$id)) -text]
5806 set xr [expr {$xt + [font measure $mainfont $text]}]
5807 if {$xr > $canvxmax} {
5811 if {[info exists selectedline]
5812 && $selectedline == $commitrow($curview,$id)} {
5813 selectline $selectedline 0
5820 catch {destroy $mktagtop}
5829 proc writecommit {} {
5830 global rowmenuid wrcomtop commitinfo wrcomcmd
5832 set top .writecommit
5834 catch {destroy $top}
5836 label $top.title -text "Write commit to file"
5837 grid $top.title - -pady 10
5838 label $top.id -text "ID:"
5839 entry $top.sha1 -width 40 -relief flat
5840 $top.sha1 insert 0 $rowmenuid
5841 $top.sha1 conf -state readonly
5842 grid $top.id $top.sha1 -sticky w
5843 entry $top.head -width 60 -relief flat
5844 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
5845 $top.head conf -state readonly
5846 grid x $top.head -sticky w
5847 label $top.clab -text "Command:"
5848 entry $top.cmd -width 60 -textvariable wrcomcmd
5849 grid $top.clab $top.cmd -sticky w -pady 10
5850 label $top.flab -text "Output file:"
5851 entry $top.fname -width 60
5852 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
5853 grid $top.flab $top.fname -sticky w
5855 button $top.buts.gen -text "Write" -command wrcomgo
5856 button $top.buts.can -text "Cancel" -command wrcomcan
5857 grid $top.buts.gen $top.buts.can
5858 grid columnconfigure $top.buts 0 -weight 1 -uniform a
5859 grid columnconfigure $top.buts 1 -weight 1 -uniform a
5860 grid $top.buts - -pady 10 -sticky ew
5867 set id [$wrcomtop.sha1 get]
5868 set cmd "echo $id | [$wrcomtop.cmd get]"
5869 set fname [$wrcomtop.fname get]
5870 if {[catch {exec sh -c $cmd >$fname &} err]} {
5871 error_popup "Error writing commit: $err"
5873 catch {destroy $wrcomtop}
5880 catch {destroy $wrcomtop}
5885 global rowmenuid mkbrtop
5888 catch {destroy $top}
5890 label $top.title -text "Create new branch"
5891 grid $top.title - -pady 10
5892 label $top.id -text "ID:"
5893 entry $top.sha1 -width 40 -relief flat
5894 $top.sha1 insert 0 $rowmenuid
5895 $top.sha1 conf -state readonly
5896 grid $top.id $top.sha1 -sticky w
5897 label $top.nlab -text "Name:"
5898 entry $top.name -width 40
5899 grid $top.nlab $top.name -sticky w
5901 button $top.buts.go -text "Create" -command [list mkbrgo $top]
5902 button $top.buts.can -text "Cancel" -command "catch {destroy $top}"
5903 grid $top.buts.go $top.buts.can
5904 grid columnconfigure $top.buts 0 -weight 1 -uniform a
5905 grid columnconfigure $top.buts 1 -weight 1 -uniform a
5906 grid $top.buts - -pady 10 -sticky ew
5911 global headids idheads
5913 set name [$top.name get]
5914 set id [$top.sha1 get]
5916 error_popup "Please specify a name for the new branch"
5919 catch {destroy $top}
5923 exec git branch $name $id
5928 set headids($name) $id
5929 lappend idheads($id) $name
5937 proc cherrypick {} {
5938 global rowmenuid curview commitrow
5941 set oldhead [exec git rev-parse HEAD]
5942 set dheads [descheads $rowmenuid]
5943 if {$dheads ne {} && [lsearch -exact $dheads $oldhead] >= 0} {
5944 set ok [confirm_popup "Commit [string range $rowmenuid 0 7] is already\
5945 included in branch $mainhead -- really re-apply it?"]
5950 # Unfortunately git-cherry-pick writes stuff to stderr even when
5951 # no error occurs, and exec takes that as an indication of error...
5952 if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
5957 set newhead [exec git rev-parse HEAD]
5958 if {$newhead eq $oldhead} {
5960 error_popup "No changes committed"
5963 addnewchild $newhead $oldhead
5964 if {[info exists commitrow($curview,$oldhead)]} {
5965 insertrow $commitrow($curview,$oldhead) $newhead
5966 if {$mainhead ne {}} {
5967 movehead $newhead $mainhead
5968 movedhead $newhead $mainhead
5977 global mainheadid mainhead rowmenuid confirm_ok resettype
5978 global showlocalchanges
5981 set w ".confirmreset"
5984 wm title $w "Confirm reset"
5985 message $w.m -text \
5986 "Reset branch $mainhead to [string range $rowmenuid 0 7]?" \
5987 -justify center -aspect 1000
5988 pack $w.m -side top -fill x -padx 20 -pady 20
5989 frame $w.f -relief sunken -border 2
5990 message $w.f.rt -text "Reset type:" -aspect 1000
5991 grid $w.f.rt -sticky w
5993 radiobutton $w.f.soft -value soft -variable resettype -justify left \
5994 -text "Soft: Leave working tree and index untouched"
5995 grid $w.f.soft -sticky w
5996 radiobutton $w.f.mixed -value mixed -variable resettype -justify left \
5997 -text "Mixed: Leave working tree untouched, reset index"
5998 grid $w.f.mixed -sticky w
5999 radiobutton $w.f.hard -value hard -variable resettype -justify left \
6000 -text "Hard: Reset working tree and index\n(discard ALL local changes)"
6001 grid $w.f.hard -sticky w
6002 pack $w.f -side top -fill x
6003 button $w.ok -text OK -command "set confirm_ok 1; destroy $w"
6004 pack $w.ok -side left -fill x -padx 20 -pady 20
6005 button $w.cancel -text Cancel -command "destroy $w"
6006 pack $w.cancel -side right -fill x -padx 20 -pady 20
6007 bind $w <Visibility> "grab $w; focus $w"
6009 if {!$confirm_ok} return
6010 if {[catch {set fd [open \
6011 [list | sh -c "git reset --$resettype $rowmenuid 2>&1"] r]} err]} {
6015 set w ".resetprogress"
6016 filerun $fd [list readresetstat $fd $w]
6019 wm title $w "Reset progress"
6020 message $w.m -text "Reset in progress, please wait..." \
6021 -justify center -aspect 1000
6022 pack $w.m -side top -fill x -padx 20 -pady 5
6023 canvas $w.c -width 150 -height 20 -bg white
6024 $w.c create rect 0 0 0 20 -fill green -tags rect
6025 pack $w.c -side top -fill x -padx 20 -pady 5 -expand 1
6030 proc readresetstat {fd w} {
6031 global mainhead mainheadid showlocalchanges
6033 if {[gets $fd line] >= 0} {
6034 if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
6035 set x [expr {($m * 150) / $n}]
6036 $w.c coords rect 0 0 $x 20
6042 if {[catch {close $fd} err]} {
6045 set oldhead $mainheadid
6046 set newhead [exec git rev-parse HEAD]
6047 if {$newhead ne $oldhead} {
6048 movehead $newhead $mainhead
6049 movedhead $newhead $mainhead
6050 set mainheadid $newhead
6054 if {$showlocalchanges} {
6060 # context menu for a head
6061 proc headmenu {x y id head} {
6062 global headmenuid headmenuhead headctxmenu mainhead
6065 set headmenuhead $head
6067 if {$head eq $mainhead} {
6070 $headctxmenu entryconfigure 0 -state $state
6071 $headctxmenu entryconfigure 1 -state $state
6072 tk_popup $headctxmenu $x $y
6076 global headmenuid headmenuhead mainhead headids
6077 global showlocalchanges mainheadid
6079 # check the tree is clean first??
6080 set oldmainhead $mainhead
6085 exec git checkout -q $headmenuhead
6091 set mainhead $headmenuhead
6092 set mainheadid $headmenuid
6093 if {[info exists headids($oldmainhead)]} {
6094 redrawtags $headids($oldmainhead)
6096 redrawtags $headmenuid
6098 if {$showlocalchanges} {
6104 global headmenuid headmenuhead mainhead
6105 global headids idheads
6107 set head $headmenuhead
6109 # this check shouldn't be needed any more...
6110 if {$head eq $mainhead} {
6111 error_popup "Cannot delete the currently checked-out branch"
6114 set dheads [descheads $id]
6115 if {$dheads eq $headids($head)} {
6116 # the stuff on this branch isn't on any other branch
6117 if {![confirm_popup "The commits on branch $head aren't on any other\
6118 branch.\nReally delete branch $head?"]} return
6122 if {[catch {exec git branch -D $head} err]} {
6127 removehead $id $head
6128 removedhead $id $head
6134 # Stuff for finding nearby tags
6135 proc getallcommits {} {
6136 global allcommits allids nbmp nextarc seeds
6146 # Called when the graph might have changed
6147 proc regetallcommits {} {
6148 global allcommits seeds
6150 set cmd [concat | git rev-list --all --parents]
6154 set fd [open $cmd r]
6155 fconfigure $fd -blocking 0
6158 filerun $fd [list getallclines $fd]
6161 # Since most commits have 1 parent and 1 child, we group strings of
6162 # such commits into "arcs" joining branch/merge points (BMPs), which
6163 # are commits that either don't have 1 parent or don't have 1 child.
6165 # arcnos(id) - incoming arcs for BMP, arc we're on for other nodes
6166 # arcout(id) - outgoing arcs for BMP
6167 # arcids(a) - list of IDs on arc including end but not start
6168 # arcstart(a) - BMP ID at start of arc
6169 # arcend(a) - BMP ID at end of arc
6170 # growing(a) - arc a is still growing
6171 # arctags(a) - IDs out of arcids (excluding end) that have tags
6172 # archeads(a) - IDs out of arcids (excluding end) that have heads
6173 # The start of an arc is at the descendent end, so "incoming" means
6174 # coming from descendents, and "outgoing" means going towards ancestors.
6176 proc getallclines {fd} {
6177 global allids allparents allchildren idtags idheads nextarc nbmp
6178 global arcnos arcids arctags arcout arcend arcstart archeads growing
6179 global seeds allcommits
6182 while {[incr nid] <= 1000 && [gets $fd line] >= 0} {
6183 set id [lindex $line 0]
6184 if {[info exists allparents($id)]} {
6189 set olds [lrange $line 1 end]
6190 set allparents($id) $olds
6191 if {![info exists allchildren($id)]} {
6192 set allchildren($id) {}
6197 if {[llength $olds] == 1 && [llength $a] == 1} {
6198 lappend arcids($a) $id
6199 if {[info exists idtags($id)]} {
6200 lappend arctags($a) $id
6202 if {[info exists idheads($id)]} {
6203 lappend archeads($a) $id
6205 if {[info exists allparents($olds)]} {
6206 # seen parent already
6207 if {![info exists arcout($olds)]} {
6210 lappend arcids($a) $olds
6211 set arcend($a) $olds
6214 lappend allchildren($olds) $id
6215 lappend arcnos($olds) $a
6220 foreach a $arcnos($id) {
6221 lappend arcids($a) $id
6228 lappend allchildren($p) $id
6229 set a [incr nextarc]
6230 set arcstart($a) $id
6237 if {[info exists allparents($p)]} {
6238 # seen it already, may need to make a new branch
6239 if {![info exists arcout($p)]} {
6242 lappend arcids($a) $p
6246 lappend arcnos($p) $a
6251 global cached_dheads cached_dtags cached_atags
6252 catch {unset cached_dheads}
6253 catch {unset cached_dtags}
6254 catch {unset cached_atags}
6257 return [expr {$nid >= 1000? 2: 1}]
6260 if {[incr allcommits -1] == 0} {
6267 proc recalcarc {a} {
6268 global arctags archeads arcids idtags idheads
6272 foreach id [lrange $arcids($a) 0 end-1] {
6273 if {[info exists idtags($id)]} {
6276 if {[info exists idheads($id)]} {
6281 set archeads($a) $ah
6285 global arcnos arcids nextarc nbmp arctags archeads idtags idheads
6286 global arcstart arcend arcout allparents growing
6289 if {[llength $a] != 1} {
6290 puts "oops splitarc called but [llength $a] arcs already"
6294 set i [lsearch -exact $arcids($a) $p]
6296 puts "oops splitarc $p not in arc $a"
6299 set na [incr nextarc]
6300 if {[info exists arcend($a)]} {
6301 set arcend($na) $arcend($a)
6303 set l [lindex $allparents([lindex $arcids($a) end]) 0]
6304 set j [lsearch -exact $arcnos($l) $a]
6305 set arcnos($l) [lreplace $arcnos($l) $j $j $na]
6307 set tail [lrange $arcids($a) [expr {$i+1}] end]
6308 set arcids($a) [lrange $arcids($a) 0 $i]
6310 set arcstart($na) $p
6312 set arcids($na) $tail
6313 if {[info exists growing($a)]} {
6320 if {[llength $arcnos($id)] == 1} {
6323 set j [lsearch -exact $arcnos($id) $a]
6324 set arcnos($id) [lreplace $arcnos($id) $j $j $na]
6328 # reconstruct tags and heads lists
6329 if {$arctags($a) ne {} || $archeads($a) ne {}} {
6334 set archeads($na) {}
6338 # Update things for a new commit added that is a child of one
6339 # existing commit. Used when cherry-picking.
6340 proc addnewchild {id p} {
6341 global allids allparents allchildren idtags nextarc nbmp
6342 global arcnos arcids arctags arcout arcend arcstart archeads growing
6346 set allparents($id) [list $p]
6347 set allchildren($id) {}
6351 lappend allchildren($p) $id
6352 set a [incr nextarc]
6353 set arcstart($a) $id
6356 set arcids($a) [list $p]
6358 if {![info exists arcout($p)]} {
6361 lappend arcnos($p) $a
6362 set arcout($id) [list $a]
6365 # Returns 1 if a is an ancestor of b, -1 if b is an ancestor of a,
6366 # or 0 if neither is true.
6367 proc anc_or_desc {a b} {
6368 global arcout arcstart arcend arcnos cached_isanc
6370 if {$arcnos($a) eq $arcnos($b)} {
6371 # Both are on the same arc(s); either both are the same BMP,
6372 # or if one is not a BMP, the other is also not a BMP or is
6373 # the BMP at end of the arc (and it only has 1 incoming arc).
6374 # Or both can be BMPs with no incoming arcs.
6375 if {$a eq $b || $arcnos($a) eq {}} {
6378 # assert {[llength $arcnos($a)] == 1}
6379 set arc [lindex $arcnos($a) 0]
6380 set i [lsearch -exact $arcids($arc) $a]
6381 set j [lsearch -exact $arcids($arc) $b]
6382 if {$i < 0 || $i > $j} {
6389 if {![info exists arcout($a)]} {
6390 set arc [lindex $arcnos($a) 0]
6391 if {[info exists arcend($arc)]} {
6392 set aend $arcend($arc)
6396 set a $arcstart($arc)
6400 if {![info exists arcout($b)]} {
6401 set arc [lindex $arcnos($b) 0]
6402 if {[info exists arcend($arc)]} {
6403 set bend $arcend($arc)
6407 set b $arcstart($arc)
6417 if {[info exists cached_isanc($a,$bend)]} {
6418 if {$cached_isanc($a,$bend)} {
6422 if {[info exists cached_isanc($b,$aend)]} {
6423 if {$cached_isanc($b,$aend)} {
6426 if {[info exists cached_isanc($a,$bend)]} {
6431 set todo [list $a $b]
6434 for {set i 0} {$i < [llength $todo]} {incr i} {
6435 set x [lindex $todo $i]
6436 if {$anc($x) eq {}} {
6439 foreach arc $arcnos($x) {
6440 set xd $arcstart($arc)
6442 set cached_isanc($a,$bend) 1
6443 set cached_isanc($b,$aend) 0
6445 } elseif {$xd eq $aend} {
6446 set cached_isanc($b,$aend) 1
6447 set cached_isanc($a,$bend) 0
6450 if {![info exists anc($xd)]} {
6451 set anc($xd) $anc($x)
6453 } elseif {$anc($xd) ne $anc($x)} {
6458 set cached_isanc($a,$bend) 0
6459 set cached_isanc($b,$aend) 0
6463 # This identifies whether $desc has an ancestor that is
6464 # a growing tip of the graph and which is not an ancestor of $anc
6465 # and returns 0 if so and 1 if not.
6466 # If we subsequently discover a tag on such a growing tip, and that
6467 # turns out to be a descendent of $anc (which it could, since we
6468 # don't necessarily see children before parents), then $desc
6469 # isn't a good choice to display as a descendent tag of
6470 # $anc (since it is the descendent of another tag which is
6471 # a descendent of $anc). Similarly, $anc isn't a good choice to
6472 # display as a ancestor tag of $desc.
6474 proc is_certain {desc anc} {
6475 global arcnos arcout arcstart arcend growing problems
6478 if {[llength $arcnos($anc)] == 1} {
6479 # tags on the same arc are certain
6480 if {$arcnos($desc) eq $arcnos($anc)} {
6483 if {![info exists arcout($anc)]} {
6484 # if $anc is partway along an arc, use the start of the arc instead
6485 set a [lindex $arcnos($anc) 0]
6486 set anc $arcstart($a)
6489 if {[llength $arcnos($desc)] > 1 || [info exists arcout($desc)]} {
6492 set a [lindex $arcnos($desc) 0]
6498 set anclist [list $x]
6502 for {set i 0} {$i < [llength $anclist] && ($nnh > 0 || $ngrowanc > 0)} {incr i} {
6503 set x [lindex $anclist $i]
6508 foreach a $arcout($x) {
6509 if {[info exists growing($a)]} {
6510 if {![info exists growanc($x)] && $dl($x)} {
6516 if {[info exists dl($y)]} {
6520 if {![info exists done($y)]} {
6523 if {[info exists growanc($x)]} {
6527 for {set k 0} {$k < [llength $xl]} {incr k} {
6528 set z [lindex $xl $k]
6529 foreach c $arcout($z) {
6530 if {[info exists arcend($c)]} {
6532 if {[info exists dl($v)] && $dl($v)} {
6534 if {![info exists done($v)]} {
6537 if {[info exists growanc($v)]} {
6547 } elseif {$y eq $anc || !$dl($x)} {
6558 foreach x [array names growanc] {
6567 proc validate_arctags {a} {
6568 global arctags idtags
6572 foreach id $arctags($a) {
6574 if {![info exists idtags($id)]} {
6575 set na [lreplace $na $i $i]
6582 proc validate_archeads {a} {
6583 global archeads idheads
6586 set na $archeads($a)
6587 foreach id $archeads($a) {
6589 if {![info exists idheads($id)]} {
6590 set na [lreplace $na $i $i]
6594 set archeads($a) $na
6597 # Return the list of IDs that have tags that are descendents of id,
6598 # ignoring IDs that are descendents of IDs already reported.
6599 proc desctags {id} {
6600 global arcnos arcstart arcids arctags idtags allparents
6601 global growing cached_dtags
6603 if {![info exists allparents($id)]} {
6606 set t1 [clock clicks -milliseconds]
6608 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
6609 # part-way along an arc; check that arc first
6610 set a [lindex $arcnos($id) 0]
6611 if {$arctags($a) ne {}} {
6613 set i [lsearch -exact $arcids($a) $id]
6615 foreach t $arctags($a) {
6616 set j [lsearch -exact $arcids($a) $t]
6624 set id $arcstart($a)
6625 if {[info exists idtags($id)]} {
6629 if {[info exists cached_dtags($id)]} {
6630 return $cached_dtags($id)
6637 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
6638 set id [lindex $todo $i]
6640 set ta [info exists hastaggedancestor($id)]
6644 # ignore tags on starting node
6645 if {!$ta && $i > 0} {
6646 if {[info exists idtags($id)]} {
6649 } elseif {[info exists cached_dtags($id)]} {
6650 set tagloc($id) $cached_dtags($id)
6654 foreach a $arcnos($id) {
6656 if {!$ta && $arctags($a) ne {}} {
6658 if {$arctags($a) ne {}} {
6659 lappend tagloc($id) [lindex $arctags($a) end]
6662 if {$ta || $arctags($a) ne {}} {
6663 set tomark [list $d]
6664 for {set j 0} {$j < [llength $tomark]} {incr j} {
6665 set dd [lindex $tomark $j]
6666 if {![info exists hastaggedancestor($dd)]} {
6667 if {[info exists done($dd)]} {
6668 foreach b $arcnos($dd) {
6669 lappend tomark $arcstart($b)
6671 if {[info exists tagloc($dd)]} {
6674 } elseif {[info exists queued($dd)]} {
6677 set hastaggedancestor($dd) 1
6681 if {![info exists queued($d)]} {
6684 if {![info exists hastaggedancestor($d)]} {
6691 foreach id [array names tagloc] {
6692 if {![info exists hastaggedancestor($id)]} {
6693 foreach t $tagloc($id) {
6694 if {[lsearch -exact $tags $t] < 0} {
6700 set t2 [clock clicks -milliseconds]
6703 # remove tags that are descendents of other tags
6704 for {set i 0} {$i < [llength $tags]} {incr i} {
6705 set a [lindex $tags $i]
6706 for {set j 0} {$j < $i} {incr j} {
6707 set b [lindex $tags $j]
6708 set r [anc_or_desc $a $b]
6710 set tags [lreplace $tags $j $j]
6713 } elseif {$r == -1} {
6714 set tags [lreplace $tags $i $i]
6721 if {[array names growing] ne {}} {
6722 # graph isn't finished, need to check if any tag could get
6723 # eclipsed by another tag coming later. Simply ignore any
6724 # tags that could later get eclipsed.
6727 if {[is_certain $t $origid]} {
6731 if {$tags eq $ctags} {
6732 set cached_dtags($origid) $tags
6737 set cached_dtags($origid) $tags
6739 set t3 [clock clicks -milliseconds]
6740 if {0 && $t3 - $t1 >= 100} {
6741 puts "iterating descendents ($loopix/[llength $todo] nodes) took\
6742 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
6748 global arcnos arcids arcout arcend arctags idtags allparents
6749 global growing cached_atags
6751 if {![info exists allparents($id)]} {
6754 set t1 [clock clicks -milliseconds]
6756 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
6757 # part-way along an arc; check that arc first
6758 set a [lindex $arcnos($id) 0]
6759 if {$arctags($a) ne {}} {
6761 set i [lsearch -exact $arcids($a) $id]
6762 foreach t $arctags($a) {
6763 set j [lsearch -exact $arcids($a) $t]
6769 if {![info exists arcend($a)]} {
6773 if {[info exists idtags($id)]} {
6777 if {[info exists cached_atags($id)]} {
6778 return $cached_atags($id)
6786 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
6787 set id [lindex $todo $i]
6789 set td [info exists hastaggeddescendent($id)]
6793 # ignore tags on starting node
6794 if {!$td && $i > 0} {
6795 if {[info exists idtags($id)]} {
6798 } elseif {[info exists cached_atags($id)]} {
6799 set tagloc($id) $cached_atags($id)
6803 foreach a $arcout($id) {
6804 if {!$td && $arctags($a) ne {}} {
6806 if {$arctags($a) ne {}} {
6807 lappend tagloc($id) [lindex $arctags($a) 0]
6810 if {![info exists arcend($a)]} continue
6812 if {$td || $arctags($a) ne {}} {
6813 set tomark [list $d]
6814 for {set j 0} {$j < [llength $tomark]} {incr j} {
6815 set dd [lindex $tomark $j]
6816 if {![info exists hastaggeddescendent($dd)]} {
6817 if {[info exists done($dd)]} {
6818 foreach b $arcout($dd) {
6819 if {[info exists arcend($b)]} {
6820 lappend tomark $arcend($b)
6823 if {[info exists tagloc($dd)]} {
6826 } elseif {[info exists queued($dd)]} {
6829 set hastaggeddescendent($dd) 1
6833 if {![info exists queued($d)]} {
6836 if {![info exists hastaggeddescendent($d)]} {
6842 set t2 [clock clicks -milliseconds]
6845 foreach id [array names tagloc] {
6846 if {![info exists hastaggeddescendent($id)]} {
6847 foreach t $tagloc($id) {
6848 if {[lsearch -exact $tags $t] < 0} {
6855 # remove tags that are ancestors of other tags
6856 for {set i 0} {$i < [llength $tags]} {incr i} {
6857 set a [lindex $tags $i]
6858 for {set j 0} {$j < $i} {incr j} {
6859 set b [lindex $tags $j]
6860 set r [anc_or_desc $a $b]
6862 set tags [lreplace $tags $j $j]
6865 } elseif {$r == 1} {
6866 set tags [lreplace $tags $i $i]
6873 if {[array names growing] ne {}} {
6874 # graph isn't finished, need to check if any tag could get
6875 # eclipsed by another tag coming later. Simply ignore any
6876 # tags that could later get eclipsed.
6879 if {[is_certain $origid $t]} {
6883 if {$tags eq $ctags} {
6884 set cached_atags($origid) $tags
6889 set cached_atags($origid) $tags
6891 set t3 [clock clicks -milliseconds]
6892 if {0 && $t3 - $t1 >= 100} {
6893 puts "iterating ancestors ($loopix/[llength $todo] nodes) took\
6894 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
6899 # Return the list of IDs that have heads that are descendents of id,
6900 # including id itself if it has a head.
6901 proc descheads {id} {
6902 global arcnos arcstart arcids archeads idheads cached_dheads
6905 if {![info exists allparents($id)]} {
6909 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
6910 # part-way along an arc; check it first
6911 set a [lindex $arcnos($id) 0]
6912 if {$archeads($a) ne {}} {
6913 validate_archeads $a
6914 set i [lsearch -exact $arcids($a) $id]
6915 foreach t $archeads($a) {
6916 set j [lsearch -exact $arcids($a) $t]
6921 set id $arcstart($a)
6927 for {set i 0} {$i < [llength $todo]} {incr i} {
6928 set id [lindex $todo $i]
6929 if {[info exists cached_dheads($id)]} {
6930 set ret [concat $ret $cached_dheads($id)]
6932 if {[info exists idheads($id)]} {
6935 foreach a $arcnos($id) {
6936 if {$archeads($a) ne {}} {
6937 validate_archeads $a
6938 if {$archeads($a) ne {}} {
6939 set ret [concat $ret $archeads($a)]
6943 if {![info exists seen($d)]} {
6950 set ret [lsort -unique $ret]
6951 set cached_dheads($origid) $ret
6952 return [concat $ret $aret]
6955 proc addedtag {id} {
6956 global arcnos arcout cached_dtags cached_atags
6958 if {![info exists arcnos($id)]} return
6959 if {![info exists arcout($id)]} {
6960 recalcarc [lindex $arcnos($id) 0]
6962 catch {unset cached_dtags}
6963 catch {unset cached_atags}
6966 proc addedhead {hid head} {
6967 global arcnos arcout cached_dheads
6969 if {![info exists arcnos($hid)]} return
6970 if {![info exists arcout($hid)]} {
6971 recalcarc [lindex $arcnos($hid) 0]
6973 catch {unset cached_dheads}
6976 proc removedhead {hid head} {
6977 global cached_dheads
6979 catch {unset cached_dheads}
6982 proc movedhead {hid head} {
6983 global arcnos arcout cached_dheads
6985 if {![info exists arcnos($hid)]} return
6986 if {![info exists arcout($hid)]} {
6987 recalcarc [lindex $arcnos($hid) 0]
6989 catch {unset cached_dheads}
6992 proc changedrefs {} {
6993 global cached_dheads cached_dtags cached_atags
6994 global arctags archeads arcnos arcout idheads idtags
6996 foreach id [concat [array names idheads] [array names idtags]] {
6997 if {[info exists arcnos($id)] && ![info exists arcout($id)]} {
6998 set a [lindex $arcnos($id) 0]
6999 if {![info exists donearc($a)]} {
7005 catch {unset cached_dtags}
7006 catch {unset cached_atags}
7007 catch {unset cached_dheads}
7010 proc rereadrefs {} {
7011 global idtags idheads idotherrefs mainhead
7013 set refids [concat [array names idtags] \
7014 [array names idheads] [array names idotherrefs]]
7015 foreach id $refids {
7016 if {![info exists ref($id)]} {
7017 set ref($id) [listrefs $id]
7020 set oldmainhead $mainhead
7023 set refids [lsort -unique [concat $refids [array names idtags] \
7024 [array names idheads] [array names idotherrefs]]]
7025 foreach id $refids {
7026 set v [listrefs $id]
7027 if {![info exists ref($id)] || $ref($id) != $v ||
7028 ($id eq $oldmainhead && $id ne $mainhead) ||
7029 ($id eq $mainhead && $id ne $oldmainhead)} {
7035 proc listrefs {id} {
7036 global idtags idheads idotherrefs
7039 if {[info exists idtags($id)]} {
7043 if {[info exists idheads($id)]} {
7047 if {[info exists idotherrefs($id)]} {
7048 set z $idotherrefs($id)
7050 return [list $x $y $z]
7053 proc showtag {tag isnew} {
7054 global ctext tagcontents tagids linknum tagobjid
7057 addtohistory [list showtag $tag 0]
7059 $ctext conf -state normal
7062 if {![info exists tagcontents($tag)]} {
7064 set tagcontents($tag) [exec git cat-file tag $tagobjid($tag)]
7067 if {[info exists tagcontents($tag)]} {
7068 set text $tagcontents($tag)
7070 set text "Tag: $tag\nId: $tagids($tag)"
7072 appendwithlinks $text {}
7073 $ctext conf -state disabled
7085 global maxwidth maxgraphpct diffopts
7086 global oldprefs prefstop showneartags showlocalchanges
7087 global bgcolor fgcolor ctext diffcolors selectbgcolor
7088 global uifont tabstop
7092 if {[winfo exists $top]} {
7096 foreach v {maxwidth maxgraphpct diffopts showneartags showlocalchanges} {
7097 set oldprefs($v) [set $v]
7100 wm title $top "Gitk preferences"
7101 label $top.ldisp -text "Commit list display options"
7102 $top.ldisp configure -font $uifont
7103 grid $top.ldisp - -sticky w -pady 10
7104 label $top.spacer -text " "
7105 label $top.maxwidthl -text "Maximum graph width (lines)" \
7107 spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
7108 grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w
7109 label $top.maxpctl -text "Maximum graph width (% of pane)" \
7111 spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
7112 grid x $top.maxpctl $top.maxpct -sticky w
7113 frame $top.showlocal
7114 label $top.showlocal.l -text "Show local changes" -font optionfont
7115 checkbutton $top.showlocal.b -variable showlocalchanges
7116 pack $top.showlocal.b $top.showlocal.l -side left
7117 grid x $top.showlocal -sticky w
7119 label $top.ddisp -text "Diff display options"
7120 $top.ddisp configure -font $uifont
7121 grid $top.ddisp - -sticky w -pady 10
7122 label $top.diffoptl -text "Options for diff program" \
7124 entry $top.diffopt -width 20 -textvariable diffopts
7125 grid x $top.diffoptl $top.diffopt -sticky w
7127 label $top.ntag.l -text "Display nearby tags" -font optionfont
7128 checkbutton $top.ntag.b -variable showneartags
7129 pack $top.ntag.b $top.ntag.l -side left
7130 grid x $top.ntag -sticky w
7131 label $top.tabstopl -text "tabstop" -font optionfont
7132 spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
7133 grid x $top.tabstopl $top.tabstop -sticky w
7135 label $top.cdisp -text "Colors: press to choose"
7136 $top.cdisp configure -font $uifont
7137 grid $top.cdisp - -sticky w -pady 10
7138 label $top.bg -padx 40 -relief sunk -background $bgcolor
7139 button $top.bgbut -text "Background" -font optionfont \
7140 -command [list choosecolor bgcolor 0 $top.bg background setbg]
7141 grid x $top.bgbut $top.bg -sticky w
7142 label $top.fg -padx 40 -relief sunk -background $fgcolor
7143 button $top.fgbut -text "Foreground" -font optionfont \
7144 -command [list choosecolor fgcolor 0 $top.fg foreground setfg]
7145 grid x $top.fgbut $top.fg -sticky w
7146 label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
7147 button $top.diffoldbut -text "Diff: old lines" -font optionfont \
7148 -command [list choosecolor diffcolors 0 $top.diffold "diff old lines" \
7149 [list $ctext tag conf d0 -foreground]]
7150 grid x $top.diffoldbut $top.diffold -sticky w
7151 label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
7152 button $top.diffnewbut -text "Diff: new lines" -font optionfont \
7153 -command [list choosecolor diffcolors 1 $top.diffnew "diff new lines" \
7154 [list $ctext tag conf d1 -foreground]]
7155 grid x $top.diffnewbut $top.diffnew -sticky w
7156 label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
7157 button $top.hunksepbut -text "Diff: hunk header" -font optionfont \
7158 -command [list choosecolor diffcolors 2 $top.hunksep \
7159 "diff hunk header" \
7160 [list $ctext tag conf hunksep -foreground]]
7161 grid x $top.hunksepbut $top.hunksep -sticky w
7162 label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
7163 button $top.selbgbut -text "Select bg" -font optionfont \
7164 -command [list choosecolor selectbgcolor 0 $top.selbgsep background setselbg]
7165 grid x $top.selbgbut $top.selbgsep -sticky w
7168 button $top.buts.ok -text "OK" -command prefsok -default active
7169 $top.buts.ok configure -font $uifont
7170 button $top.buts.can -text "Cancel" -command prefscan -default normal
7171 $top.buts.can configure -font $uifont
7172 grid $top.buts.ok $top.buts.can
7173 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7174 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7175 grid $top.buts - - -pady 10 -sticky ew
7176 bind $top <Visibility> "focus $top.buts.ok"
7179 proc choosecolor {v vi w x cmd} {
7182 set c [tk_chooseColor -initialcolor [lindex [set $v] $vi] \
7183 -title "Gitk: choose color for $x"]
7184 if {$c eq {}} return
7185 $w conf -background $c
7191 global bglist cflist
7193 $w configure -selectbackground $c
7195 $cflist tag configure highlight \
7196 -background [$cflist cget -selectbackground]
7197 allcanvs itemconf secsel -fill $c
7204 $w conf -background $c
7212 $w conf -foreground $c
7214 allcanvs itemconf text -fill $c
7215 $canv itemconf circle -outline $c
7219 global maxwidth maxgraphpct diffopts
7220 global oldprefs prefstop showneartags showlocalchanges
7222 foreach v {maxwidth maxgraphpct diffopts showneartags showlocalchanges} {
7223 set $v $oldprefs($v)
7225 catch {destroy $prefstop}
7230 global maxwidth maxgraphpct
7231 global oldprefs prefstop showneartags showlocalchanges
7232 global charspc ctext tabstop
7234 catch {destroy $prefstop}
7236 $ctext configure -tabs "[expr {$tabstop * $charspc}]"
7237 if {$showlocalchanges != $oldprefs(showlocalchanges)} {
7238 if {$showlocalchanges} {
7244 if {$maxwidth != $oldprefs(maxwidth)
7245 || $maxgraphpct != $oldprefs(maxgraphpct)} {
7247 } elseif {$showneartags != $oldprefs(showneartags)} {
7252 proc formatdate {d} {
7254 set d [clock format $d -format "%Y-%m-%d %H:%M:%S"]
7259 # This list of encoding names and aliases is distilled from
7260 # http://www.iana.org/assignments/character-sets.
7261 # Not all of them are supported by Tcl.
7262 set encoding_aliases {
7263 { ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ASCII
7264 ISO646-US US-ASCII us IBM367 cp367 csASCII }
7265 { ISO-10646-UTF-1 csISO10646UTF1 }
7266 { ISO_646.basic:1983 ref csISO646basic1983 }
7267 { INVARIANT csINVARIANT }
7268 { ISO_646.irv:1983 iso-ir-2 irv csISO2IntlRefVersion }
7269 { BS_4730 iso-ir-4 ISO646-GB gb uk csISO4UnitedKingdom }
7270 { NATS-SEFI iso-ir-8-1 csNATSSEFI }
7271 { NATS-SEFI-ADD iso-ir-8-2 csNATSSEFIADD }
7272 { NATS-DANO iso-ir-9-1 csNATSDANO }
7273 { NATS-DANO-ADD iso-ir-9-2 csNATSDANOADD }
7274 { SEN_850200_B iso-ir-10 FI ISO646-FI ISO646-SE se csISO10Swedish }
7275 { SEN_850200_C iso-ir-11 ISO646-SE2 se2 csISO11SwedishForNames }
7276 { KS_C_5601-1987 iso-ir-149 KS_C_5601-1989 KSC_5601 korean csKSC56011987 }
7277 { ISO-2022-KR csISO2022KR }
7279 { ISO-2022-JP csISO2022JP }
7280 { ISO-2022-JP-2 csISO2022JP2 }
7281 { JIS_C6220-1969-jp JIS_C6220-1969 iso-ir-13 katakana x0201-7
7283 { JIS_C6220-1969-ro iso-ir-14 jp ISO646-JP csISO14JISC6220ro }
7284 { IT iso-ir-15 ISO646-IT csISO15Italian }
7285 { PT iso-ir-16 ISO646-PT csISO16Portuguese }
7286 { ES iso-ir-17 ISO646-ES csISO17Spanish }
7287 { greek7-old iso-ir-18 csISO18Greek7Old }
7288 { latin-greek iso-ir-19 csISO19LatinGreek }
7289 { DIN_66003 iso-ir-21 de ISO646-DE csISO21German }
7290 { NF_Z_62-010_(1973) iso-ir-25 ISO646-FR1 csISO25French }
7291 { Latin-greek-1 iso-ir-27 csISO27LatinGreek1 }
7292 { ISO_5427 iso-ir-37 csISO5427Cyrillic }
7293 { JIS_C6226-1978 iso-ir-42 csISO42JISC62261978 }
7294 { BS_viewdata iso-ir-47 csISO47BSViewdata }
7295 { INIS iso-ir-49 csISO49INIS }
7296 { INIS-8 iso-ir-50 csISO50INIS8 }
7297 { INIS-cyrillic iso-ir-51 csISO51INISCyrillic }
7298 { ISO_5427:1981 iso-ir-54 ISO5427Cyrillic1981 }
7299 { ISO_5428:1980 iso-ir-55 csISO5428Greek }
7300 { GB_1988-80 iso-ir-57 cn ISO646-CN csISO57GB1988 }
7301 { GB_2312-80 iso-ir-58 chinese csISO58GB231280 }
7302 { NS_4551-1 iso-ir-60 ISO646-NO no csISO60DanishNorwegian
7304 { NS_4551-2 ISO646-NO2 iso-ir-61 no2 csISO61Norwegian2 }
7305 { NF_Z_62-010 iso-ir-69 ISO646-FR fr csISO69French }
7306 { videotex-suppl iso-ir-70 csISO70VideotexSupp1 }
7307 { PT2 iso-ir-84 ISO646-PT2 csISO84Portuguese2 }
7308 { ES2 iso-ir-85 ISO646-ES2 csISO85Spanish2 }
7309 { MSZ_7795.3 iso-ir-86 ISO646-HU hu csISO86Hungarian }
7310 { JIS_C6226-1983 iso-ir-87 x0208 JIS_X0208-1983 csISO87JISX0208 }
7311 { greek7 iso-ir-88 csISO88Greek7 }
7312 { ASMO_449 ISO_9036 arabic7 iso-ir-89 csISO89ASMO449 }
7313 { iso-ir-90 csISO90 }
7314 { JIS_C6229-1984-a iso-ir-91 jp-ocr-a csISO91JISC62291984a }
7315 { JIS_C6229-1984-b iso-ir-92 ISO646-JP-OCR-B jp-ocr-b
7316 csISO92JISC62991984b }
7317 { JIS_C6229-1984-b-add iso-ir-93 jp-ocr-b-add csISO93JIS62291984badd }
7318 { JIS_C6229-1984-hand iso-ir-94 jp-ocr-hand csISO94JIS62291984hand }
7319 { JIS_C6229-1984-hand-add iso-ir-95 jp-ocr-hand-add
7320 csISO95JIS62291984handadd }
7321 { JIS_C6229-1984-kana iso-ir-96 csISO96JISC62291984kana }
7322 { ISO_2033-1983 iso-ir-98 e13b csISO2033 }
7323 { ANSI_X3.110-1983 iso-ir-99 CSA_T500-1983 NAPLPS csISO99NAPLPS }
7324 { ISO_8859-1:1987 iso-ir-100 ISO_8859-1 ISO-8859-1 latin1 l1 IBM819
7326 { ISO_8859-2:1987 iso-ir-101 ISO_8859-2 ISO-8859-2 latin2 l2 csISOLatin2 }
7327 { T.61-7bit iso-ir-102 csISO102T617bit }
7328 { T.61-8bit T.61 iso-ir-103 csISO103T618bit }
7329 { ISO_8859-3:1988 iso-ir-109 ISO_8859-3 ISO-8859-3 latin3 l3 csISOLatin3 }
7330 { ISO_8859-4:1988 iso-ir-110 ISO_8859-4 ISO-8859-4 latin4 l4 csISOLatin4 }
7331 { ECMA-cyrillic iso-ir-111 KOI8-E csISO111ECMACyrillic }
7332 { CSA_Z243.4-1985-1 iso-ir-121 ISO646-CA csa7-1 ca csISO121Canadian1 }
7333 { CSA_Z243.4-1985-2 iso-ir-122 ISO646-CA2 csa7-2 csISO122Canadian2 }
7334 { CSA_Z243.4-1985-gr iso-ir-123 csISO123CSAZ24341985gr }
7335 { ISO_8859-6:1987 iso-ir-127 ISO_8859-6 ISO-8859-6 ECMA-114 ASMO-708
7336 arabic csISOLatinArabic }
7337 { ISO_8859-6-E csISO88596E ISO-8859-6-E }
7338 { ISO_8859-6-I csISO88596I ISO-8859-6-I }
7339 { ISO_8859-7:1987 iso-ir-126 ISO_8859-7 ISO-8859-7 ELOT_928 ECMA-118
7340 greek greek8 csISOLatinGreek }
7341 { T.101-G2 iso-ir-128 csISO128T101G2 }
7342 { ISO_8859-8:1988 iso-ir-138 ISO_8859-8 ISO-8859-8 hebrew
7344 { ISO_8859-8-E csISO88598E ISO-8859-8-E }
7345 { ISO_8859-8-I csISO88598I ISO-8859-8-I }
7346 { CSN_369103 iso-ir-139 csISO139CSN369103 }
7347 { JUS_I.B1.002 iso-ir-141 ISO646-YU js yu csISO141JUSIB1002 }
7348 { ISO_6937-2-add iso-ir-142 csISOTextComm }
7349 { IEC_P27-1 iso-ir-143 csISO143IECP271 }
7350 { ISO_8859-5:1988 iso-ir-144 ISO_8859-5 ISO-8859-5 cyrillic
7351 csISOLatinCyrillic }
7352 { JUS_I.B1.003-serb iso-ir-146 serbian csISO146Serbian }
7353 { JUS_I.B1.003-mac macedonian iso-ir-147 csISO147Macedonian }
7354 { ISO_8859-9:1989 iso-ir-148 ISO_8859-9 ISO-8859-9 latin5 l5 csISOLatin5 }
7355 { greek-ccitt iso-ir-150 csISO150 csISO150GreekCCITT }
7356 { NC_NC00-10:81 cuba iso-ir-151 ISO646-CU csISO151Cuba }
7357 { ISO_6937-2-25 iso-ir-152 csISO6937Add }
7358 { GOST_19768-74 ST_SEV_358-88 iso-ir-153 csISO153GOST1976874 }
7359 { ISO_8859-supp iso-ir-154 latin1-2-5 csISO8859Supp }
7360 { ISO_10367-box iso-ir-155 csISO10367Box }
7361 { ISO-8859-10 iso-ir-157 l6 ISO_8859-10:1992 csISOLatin6 latin6 }
7362 { latin-lap lap iso-ir-158 csISO158Lap }
7363 { JIS_X0212-1990 x0212 iso-ir-159 csISO159JISX02121990 }
7364 { DS_2089 DS2089 ISO646-DK dk csISO646Danish }
7367 { JIS_X0201 X0201 csHalfWidthKatakana }
7368 { KSC5636 ISO646-KR csKSC5636 }
7369 { ISO-10646-UCS-2 csUnicode }
7370 { ISO-10646-UCS-4 csUCS4 }
7371 { DEC-MCS dec csDECMCS }
7372 { hp-roman8 roman8 r8 csHPRoman8 }
7373 { macintosh mac csMacintosh }
7374 { IBM037 cp037 ebcdic-cp-us ebcdic-cp-ca ebcdic-cp-wt ebcdic-cp-nl
7376 { IBM038 EBCDIC-INT cp038 csIBM038 }
7377 { IBM273 CP273 csIBM273 }
7378 { IBM274 EBCDIC-BE CP274 csIBM274 }
7379 { IBM275 EBCDIC-BR cp275 csIBM275 }
7380 { IBM277 EBCDIC-CP-DK EBCDIC-CP-NO csIBM277 }
7381 { IBM278 CP278 ebcdic-cp-fi ebcdic-cp-se csIBM278 }
7382 { IBM280 CP280 ebcdic-cp-it csIBM280 }
7383 { IBM281 EBCDIC-JP-E cp281 csIBM281 }
7384 { IBM284 CP284 ebcdic-cp-es csIBM284 }
7385 { IBM285 CP285 ebcdic-cp-gb csIBM285 }
7386 { IBM290 cp290 EBCDIC-JP-kana csIBM290 }
7387 { IBM297 cp297 ebcdic-cp-fr csIBM297 }
7388 { IBM420 cp420 ebcdic-cp-ar1 csIBM420 }
7389 { IBM423 cp423 ebcdic-cp-gr csIBM423 }
7390 { IBM424 cp424 ebcdic-cp-he csIBM424 }
7391 { IBM437 cp437 437 csPC8CodePage437 }
7392 { IBM500 CP500 ebcdic-cp-be ebcdic-cp-ch csIBM500 }
7393 { IBM775 cp775 csPC775Baltic }
7394 { IBM850 cp850 850 csPC850Multilingual }
7395 { IBM851 cp851 851 csIBM851 }
7396 { IBM852 cp852 852 csPCp852 }
7397 { IBM855 cp855 855 csIBM855 }
7398 { IBM857 cp857 857 csIBM857 }
7399 { IBM860 cp860 860 csIBM860 }
7400 { IBM861 cp861 861 cp-is csIBM861 }
7401 { IBM862 cp862 862 csPC862LatinHebrew }
7402 { IBM863 cp863 863 csIBM863 }
7403 { IBM864 cp864 csIBM864 }
7404 { IBM865 cp865 865 csIBM865 }
7405 { IBM866 cp866 866 csIBM866 }
7406 { IBM868 CP868 cp-ar csIBM868 }
7407 { IBM869 cp869 869 cp-gr csIBM869 }
7408 { IBM870 CP870 ebcdic-cp-roece ebcdic-cp-yu csIBM870 }
7409 { IBM871 CP871 ebcdic-cp-is csIBM871 }
7410 { IBM880 cp880 EBCDIC-Cyrillic csIBM880 }
7411 { IBM891 cp891 csIBM891 }
7412 { IBM903 cp903 csIBM903 }
7413 { IBM904 cp904 904 csIBBM904 }
7414 { IBM905 CP905 ebcdic-cp-tr csIBM905 }
7415 { IBM918 CP918 ebcdic-cp-ar2 csIBM918 }
7416 { IBM1026 CP1026 csIBM1026 }
7417 { EBCDIC-AT-DE csIBMEBCDICATDE }
7418 { EBCDIC-AT-DE-A csEBCDICATDEA }
7419 { EBCDIC-CA-FR csEBCDICCAFR }
7420 { EBCDIC-DK-NO csEBCDICDKNO }
7421 { EBCDIC-DK-NO-A csEBCDICDKNOA }
7422 { EBCDIC-FI-SE csEBCDICFISE }
7423 { EBCDIC-FI-SE-A csEBCDICFISEA }
7424 { EBCDIC-FR csEBCDICFR }
7425 { EBCDIC-IT csEBCDICIT }
7426 { EBCDIC-PT csEBCDICPT }
7427 { EBCDIC-ES csEBCDICES }
7428 { EBCDIC-ES-A csEBCDICESA }
7429 { EBCDIC-ES-S csEBCDICESS }
7430 { EBCDIC-UK csEBCDICUK }
7431 { EBCDIC-US csEBCDICUS }
7432 { UNKNOWN-8BIT csUnknown8BiT }
7433 { MNEMONIC csMnemonic }
7438 { IBM00858 CCSID00858 CP00858 PC-Multilingual-850+euro }
7439 { IBM00924 CCSID00924 CP00924 ebcdic-Latin9--euro }
7440 { IBM01140 CCSID01140 CP01140 ebcdic-us-37+euro }
7441 { IBM01141 CCSID01141 CP01141 ebcdic-de-273+euro }
7442 { IBM01142 CCSID01142 CP01142 ebcdic-dk-277+euro ebcdic-no-277+euro }
7443 { IBM01143 CCSID01143 CP01143 ebcdic-fi-278+euro ebcdic-se-278+euro }
7444 { IBM01144 CCSID01144 CP01144 ebcdic-it-280+euro }
7445 { IBM01145 CCSID01145 CP01145 ebcdic-es-284+euro }
7446 { IBM01146 CCSID01146 CP01146 ebcdic-gb-285+euro }
7447 { IBM01147 CCSID01147 CP01147 ebcdic-fr-297+euro }
7448 { IBM01148 CCSID01148 CP01148 ebcdic-international-500+euro }
7449 { IBM01149 CCSID01149 CP01149 ebcdic-is-871+euro }
7450 { IBM1047 IBM-1047 }
7451 { PTCP154 csPTCP154 PT154 CP154 Cyrillic-Asian }
7452 { Amiga-1251 Ami1251 Amiga1251 Ami-1251 }
7453 { UNICODE-1-1 csUnicode11 }
7456 { UNICODE-1-1-UTF-7 csUnicode11UTF7 }
7457 { ISO-8859-14 iso-ir-199 ISO_8859-14:1998 ISO_8859-14 latin8 iso-celtic
7459 { ISO-8859-15 ISO_8859-15 Latin-9 }
7460 { ISO-8859-16 iso-ir-226 ISO_8859-16:2001 ISO_8859-16 latin10 l10 }
7461 { GBK CP936 MS936 windows-936 }
7462 { JIS_Encoding csJISEncoding }
7463 { Shift_JIS MS_Kanji csShiftJIS }
7464 { Extended_UNIX_Code_Packed_Format_for_Japanese csEUCPkdFmtJapanese
7466 { Extended_UNIX_Code_Fixed_Width_for_Japanese csEUCFixWidJapanese }
7467 { ISO-10646-UCS-Basic csUnicodeASCII }
7468 { ISO-10646-Unicode-Latin1 csUnicodeLatin1 ISO-10646 }
7469 { ISO-Unicode-IBM-1261 csUnicodeIBM1261 }
7470 { ISO-Unicode-IBM-1268 csUnicodeIBM1268 }
7471 { ISO-Unicode-IBM-1276 csUnicodeIBM1276 }
7472 { ISO-Unicode-IBM-1264 csUnicodeIBM1264 }
7473 { ISO-Unicode-IBM-1265 csUnicodeIBM1265 }
7474 { ISO-8859-1-Windows-3.0-Latin-1 csWindows30Latin1 }
7475 { ISO-8859-1-Windows-3.1-Latin-1 csWindows31Latin1 }
7476 { ISO-8859-2-Windows-Latin-2 csWindows31Latin2 }
7477 { ISO-8859-9-Windows-Latin-5 csWindows31Latin5 }
7478 { Adobe-Standard-Encoding csAdobeStandardEncoding }
7479 { Ventura-US csVenturaUS }
7480 { Ventura-International csVenturaInternational }
7481 { PC8-Danish-Norwegian csPC8DanishNorwegian }
7482 { PC8-Turkish csPC8Turkish }
7483 { IBM-Symbols csIBMSymbols }
7484 { IBM-Thai csIBMThai }
7485 { HP-Legal csHPLegal }
7486 { HP-Pi-font csHPPiFont }
7487 { HP-Math8 csHPMath8 }
7488 { Adobe-Symbol-Encoding csHPPSMath }
7489 { HP-DeskTop csHPDesktop }
7490 { Ventura-Math csVenturaMath }
7491 { Microsoft-Publishing csMicrosoftPublishing }
7492 { Windows-31J csWindows31J }
7497 proc tcl_encoding {enc} {
7498 global encoding_aliases
7499 set names [encoding names]
7500 set lcnames [string tolower $names]
7501 set enc [string tolower $enc]
7502 set i [lsearch -exact $lcnames $enc]
7504 # look for "isonnn" instead of "iso-nnn" or "iso_nnn"
7505 if {[regsub {^iso[-_]} $enc iso encx]} {
7506 set i [lsearch -exact $lcnames $encx]
7510 foreach l $encoding_aliases {
7511 set ll [string tolower $l]
7512 if {[lsearch -exact $ll $enc] < 0} continue
7513 # look through the aliases for one that tcl knows about
7515 set i [lsearch -exact $lcnames $e]
7517 if {[regsub {^iso[-_]} $e iso ex]} {
7518 set i [lsearch -exact $lcnames $ex]
7527 return [lindex $names $i]
7534 set diffopts "-U 5 -p"
7535 set wrcomcmd "git diff-tree --stdin -p --pretty"
7539 set gitencoding [exec git config --get i18n.commitencoding]
7541 if {$gitencoding == ""} {
7542 set gitencoding "utf-8"
7544 set tclencoding [tcl_encoding $gitencoding]
7545 if {$tclencoding == {}} {
7546 puts stderr "Warning: encoding $gitencoding is not supported by Tcl/Tk"
7549 set mainfont {Helvetica 9}
7550 set textfont {Courier 9}
7551 set uifont {Helvetica 9 bold}
7553 set findmergefiles 0
7561 set cmitmode "patch"
7562 set wrapcomment "none"
7566 set showlocalchanges 1
7568 set colors {green red blue magenta darkgrey brown orange}
7571 set diffcolors {red "#00a000" blue}
7572 set selectbgcolor gray85
7574 catch {source ~/.gitk}
7576 font create optionfont -family sans-serif -size -12
7578 # check that we can find a .git directory somewhere...
7580 if {![file isdirectory $gitdir]} {
7581 show_error {} . "Cannot find the git directory \"$gitdir\"."
7586 set cmdline_files {}
7591 "-d" { set datemode 1 }
7593 set cmdline_files [lrange $argv [expr {$i + 1}] end]
7597 lappend revtreeargs $arg
7603 if {$i >= [llength $argv] && $revtreeargs ne {}} {
7604 # no -- on command line, but some arguments (other than -d)
7606 set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
7607 set cmdline_files [split $f "\n"]
7608 set n [llength $cmdline_files]
7609 set revtreeargs [lrange $revtreeargs 0 end-$n]
7610 # Unfortunately git rev-parse doesn't produce an error when
7611 # something is both a revision and a filename. To be consistent
7612 # with git log and git rev-list, check revtreeargs for filenames.
7613 foreach arg $revtreeargs {
7614 if {[file exists $arg]} {
7615 show_error {} . "Ambiguous argument '$arg': both revision\
7621 # unfortunately we get both stdout and stderr in $err,
7622 # so look for "fatal:".
7623 set i [string first "fatal:" $err]
7625 set err [string range $err [expr {$i + 6}] end]
7627 show_error {} . "Bad arguments to gitk:\n$err"
7632 set nullid "0000000000000000000000000000000000000000"
7633 set nullid2 "0000000000000000000000000000000000000001"
7641 set highlight_paths {}
7642 set searchdirn -forwards
7646 set markingmatches 0
7653 set selectedhlview None
7662 set lookingforhead 0
7668 # wait for the window to become visible
7670 wm title . "[file tail $argv0]: [file tail [pwd]]"
7673 if {$cmdline_files ne {} || $revtreeargs ne {}} {
7674 # create a view for the files/dirs specified on the command line
7678 set viewname(1) "Command line"
7679 set viewfiles(1) $cmdline_files
7680 set viewargs(1) $revtreeargs
7683 .bar.view entryconf Edit* -state normal
7684 .bar.view entryconf Delete* -state normal
7687 if {[info exists permviews]} {
7688 foreach v $permviews {
7691 set viewname($n) [lindex $v 0]
7692 set viewfiles($n) [lindex $v 1]
7693 set viewargs($n) [lindex $v 2]