2 # Tcl ignores the next line -*- tcl -*- \
5 # Copyright (C) 2005-2006 Paul Mackerras. All rights reserved.
6 # This program is free software; it may be used, copied, modified
7 # and distributed under the terms of the GNU General Public Licence,
8 # either version 2, or (at your option) any later version.
12 if {[info exists env(GIT_DIR)]} {
15 return [exec git rev-parse --git-dir]
19 # A simple scheduler for compute-intensive stuff.
20 # The aim is to make sure that event handlers for GUI actions can
21 # run at least every 50-100 ms. Unfortunately fileevent handlers are
22 # run before X event handlers, so reading from a fast source can
23 # make the GUI completely unresponsive.
28 if {[info exists isonrunq($script)]} return
32 lappend runq [list {} $script]
33 set isonrunq($script) 1
36 proc filerun {fd script} {
37 fileevent $fd readable [list filereadable $fd $script]
40 proc filereadable {fd script} {
43 fileevent $fd readable {}
47 lappend runq [list $fd $script]
53 set tstart [clock clicks -milliseconds]
56 set fd [lindex $runq 0 0]
57 set script [lindex $runq 0 1]
58 set repeat [eval $script]
59 set t1 [clock clicks -milliseconds]
60 set t [expr {$t1 - $t0}]
61 set runq [lrange $runq 1 end]
62 if {$repeat ne {} && $repeat} {
63 if {$fd eq {} || $repeat == 2} {
64 # script returns 1 if it wants to be readded
65 # file readers return 2 if they could do more straight away
66 lappend runq [list $fd $script]
68 fileevent $fd readable [list filereadable $fd $script]
70 } elseif {$fd eq {}} {
71 unset isonrunq($script)
74 if {$t1 - $tstart >= 80} break
81 # Start off a git rev-list process and arrange to read its output
82 proc start_rev_list {view} {
84 global commfd leftover tclencoding datemode
85 global viewargs viewfiles commitidx
86 global lookingforhead showlocalchanges
88 set startmsecs [clock clicks -milliseconds]
89 set commitidx($view) 0
90 set order "--topo-order"
92 set order "--date-order"
95 set fd [open [concat | git log -z --pretty=raw $order --parents \
96 --boundary $viewargs($view) "--" $viewfiles($view)] r]
98 error_popup "Error executing git rev-list: $err"
101 set commfd($view) $fd
102 set leftover($view) {}
103 set lookingforhead $showlocalchanges
104 fconfigure $fd -blocking 0 -translation lf -eofchar {}
105 if {$tclencoding != {}} {
106 fconfigure $fd -encoding $tclencoding
108 filerun $fd [list getcommitlines $fd $view]
112 proc stop_rev_list {} {
113 global commfd curview
115 if {![info exists commfd($curview)]} return
116 set fd $commfd($curview)
122 unset commfd($curview)
126 global phase canv mainfont curview
130 start_rev_list $curview
131 show_status "Reading commits..."
134 proc getcommitlines {fd view} {
136 global leftover commfd
137 global displayorder commitidx commitrow commitdata
138 global parentlist children curview hlview
139 global vparentlist vdisporder vcmitlisted
141 set stuff [read $fd 500000]
142 # git log doesn't terminate the last commit with a null...
143 if {$stuff == {} && $leftover($view) ne {} && [eof $fd]} {
153 # set it blocking so we wait for the process to terminate
154 fconfigure $fd -blocking 1
155 if {[catch {close $fd} err]} {
157 if {$view != $curview} {
158 set fv " for the \"$viewname($view)\" view"
160 if {[string range $err 0 4] == "usage"} {
161 set err "Gitk: error reading commits$fv:\
162 bad arguments to git rev-list."
163 if {$viewname($view) eq "Command line"} {
165 " (Note: arguments to gitk are passed to git rev-list\
166 to allow selection of commits to be displayed.)"
169 set err "Error reading commits$fv: $err"
173 if {$view == $curview} {
174 run chewcommits $view
181 set i [string first "\0" $stuff $start]
183 append leftover($view) [string range $stuff $start end]
187 set cmit $leftover($view)
188 append cmit [string range $stuff 0 [expr {$i - 1}]]
189 set leftover($view) {}
191 set cmit [string range $stuff $start [expr {$i - 1}]]
193 set start [expr {$i + 1}]
194 set j [string first "\n" $cmit]
197 if {$j >= 0 && [string match "commit *" $cmit]} {
198 set ids [string range $cmit 7 [expr {$j - 1}]]
199 if {[string match {[-<>]*} $ids]} {
200 switch -- [string index $ids 0] {
205 set ids [string range $ids 1 end]
209 if {[string length $id] != 40} {
217 if {[string length $shortcmit] > 80} {
218 set shortcmit "[string range $shortcmit 0 80]..."
220 error_popup "Can't parse git log output: {$shortcmit}"
223 set id [lindex $ids 0]
225 set olds [lrange $ids 1 end]
228 if {$i == 0 || [lsearch -exact $olds $p] >= $i} {
229 lappend children($view,$p) $id
236 if {![info exists children($view,$id)]} {
237 set children($view,$id) {}
239 set commitdata($id) [string range $cmit [expr {$j + 1}] end]
240 set commitrow($view,$id) $commitidx($view)
241 incr commitidx($view)
242 if {$view == $curview} {
243 lappend parentlist $olds
244 lappend displayorder $id
245 lappend commitlisted $listed
247 lappend vparentlist($view) $olds
248 lappend vdisporder($view) $id
249 lappend vcmitlisted($view) $listed
254 run chewcommits $view
259 proc chewcommits {view} {
260 global curview hlview commfd
261 global selectedline pending_select
264 if {$view == $curview} {
265 set allread [expr {![info exists commfd($view)]}]
266 set tlimit [expr {[clock clicks -milliseconds] + 50}]
267 set more [layoutmore $tlimit $allread]
268 if {$allread && !$more} {
269 global displayorder commitidx phase
270 global numcommits startmsecs
272 if {[info exists pending_select]} {
273 set row [first_real_row]
276 if {$commitidx($curview) > 0} {
277 #set ms [expr {[clock clicks -milliseconds] - $startmsecs}]
278 #puts "overall $ms ms for $numcommits commits"
280 show_status "No commits selected"
286 if {[info exists hlview] && $view == $hlview} {
292 proc readcommit {id} {
293 if {[catch {set contents [exec git cat-file commit $id]}]} return
294 parsecommit $id $contents 0
297 proc updatecommits {} {
298 global viewdata curview phase displayorder
299 global children commitrow selectedline thickerline
306 foreach id $displayorder {
307 catch {unset children($n,$id)}
308 catch {unset commitrow($n,$id)}
311 catch {unset selectedline}
312 catch {unset thickerline}
313 catch {unset viewdata($n)}
320 proc parsecommit {id contents listed} {
321 global commitinfo cdate
330 set hdrend [string first "\n\n" $contents]
332 # should never happen...
333 set hdrend [string length $contents]
335 set header [string range $contents 0 [expr {$hdrend - 1}]]
336 set comment [string range $contents [expr {$hdrend + 2}] end]
337 foreach line [split $header "\n"] {
338 set tag [lindex $line 0]
339 if {$tag == "author"} {
340 set audate [lindex $line end-1]
341 set auname [lrange $line 1 end-2]
342 } elseif {$tag == "committer"} {
343 set comdate [lindex $line end-1]
344 set comname [lrange $line 1 end-2]
348 # take the first non-blank line of the comment as the headline
349 set headline [string trimleft $comment]
350 set i [string first "\n" $headline]
352 set headline [string range $headline 0 $i]
354 set headline [string trimright $headline]
355 set i [string first "\r" $headline]
357 set headline [string trimright [string range $headline 0 $i]]
360 # git rev-list indents the comment by 4 spaces;
361 # if we got this via git cat-file, add the indentation
363 foreach line [split $comment "\n"] {
364 append newcomment " "
365 append newcomment $line
366 append newcomment "\n"
368 set comment $newcomment
370 if {$comdate != {}} {
371 set cdate($id) $comdate
373 set commitinfo($id) [list $headline $auname $audate \
374 $comname $comdate $comment]
377 proc getcommit {id} {
378 global commitdata commitinfo
380 if {[info exists commitdata($id)]} {
381 parsecommit $id $commitdata($id) 1
384 if {![info exists commitinfo($id)]} {
385 set commitinfo($id) {"No commit information available"}
392 global tagids idtags headids idheads tagobjid
393 global otherrefids idotherrefs mainhead mainheadid
395 foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
398 set refd [open [list | git show-ref -d] r]
399 while {[gets $refd line] >= 0} {
400 if {[string index $line 40] ne " "} continue
401 set id [string range $line 0 39]
402 set ref [string range $line 41 end]
403 if {![string match "refs/*" $ref]} continue
404 set name [string range $ref 5 end]
405 if {[string match "remotes/*" $name]} {
406 if {![string match "*/HEAD" $name]} {
407 set headids($name) $id
408 lappend idheads($id) $name
410 } elseif {[string match "heads/*" $name]} {
411 set name [string range $name 6 end]
412 set headids($name) $id
413 lappend idheads($id) $name
414 } elseif {[string match "tags/*" $name]} {
415 # this lets refs/tags/foo^{} overwrite refs/tags/foo,
416 # which is what we want since the former is the commit ID
417 set name [string range $name 5 end]
418 if {[string match "*^{}" $name]} {
419 set name [string range $name 0 end-3]
421 set tagobjid($name) $id
423 set tagids($name) $id
424 lappend idtags($id) $name
426 set otherrefids($name) $id
427 lappend idotherrefs($id) $name
434 set thehead [exec git symbolic-ref HEAD]
435 if {[string match "refs/heads/*" $thehead]} {
436 set mainhead [string range $thehead 11 end]
437 if {[info exists headids($mainhead)]} {
438 set mainheadid $headids($mainhead)
444 # skip over fake commits
445 proc first_real_row {} {
446 global nullid nullid2 displayorder numcommits
448 for {set row 0} {$row < $numcommits} {incr row} {
449 set id [lindex $displayorder $row]
450 if {$id ne $nullid && $id ne $nullid2} {
457 # update things for a head moved to a child of its previous location
458 proc movehead {id name} {
459 global headids idheads
461 removehead $headids($name) $name
462 set headids($name) $id
463 lappend idheads($id) $name
466 # update things when a head has been removed
467 proc removehead {id name} {
468 global headids idheads
470 if {$idheads($id) eq $name} {
473 set i [lsearch -exact $idheads($id) $name]
475 set idheads($id) [lreplace $idheads($id) $i $i]
481 proc show_error {w top msg} {
482 message $w.m -text $msg -justify center -aspect 400
483 pack $w.m -side top -fill x -padx 20 -pady 20
484 button $w.ok -text OK -command "destroy $top"
485 pack $w.ok -side bottom -fill x
486 bind $top <Visibility> "grab $top; focus $top"
487 bind $top <Key-Return> "destroy $top"
491 proc error_popup msg {
495 show_error $w $w $msg
498 proc confirm_popup msg {
504 message $w.m -text $msg -justify center -aspect 400
505 pack $w.m -side top -fill x -padx 20 -pady 20
506 button $w.ok -text OK -command "set confirm_ok 1; destroy $w"
507 pack $w.ok -side left -fill x
508 button $w.cancel -text Cancel -command "destroy $w"
509 pack $w.cancel -side right -fill x
510 bind $w <Visibility> "grab $w; focus $w"
516 global canv canv2 canv3 linespc charspc ctext cflist
517 global textfont mainfont uifont tabstop
518 global findtype findtypemenu findloc findstring fstring geometry
519 global entries sha1entry sha1string sha1but
520 global maincursor textcursor curtextcursor
521 global rowctxmenu fakerowmenu mergemax wrapcomment
522 global highlight_files gdttype
523 global searchstring sstring
524 global bgcolor fgcolor bglist fglist diffcolors selectbgcolor
528 .bar add cascade -label "File" -menu .bar.file
529 .bar configure -font $uifont
531 .bar.file add command -label "Update" -command updatecommits
532 .bar.file add command -label "Reread references" -command rereadrefs
533 .bar.file add command -label "Quit" -command doquit
534 .bar.file configure -font $uifont
536 .bar add cascade -label "Edit" -menu .bar.edit
537 .bar.edit add command -label "Preferences" -command doprefs
538 .bar.edit configure -font $uifont
540 menu .bar.view -font $uifont
541 .bar add cascade -label "View" -menu .bar.view
542 .bar.view add command -label "New view..." -command {newview 0}
543 .bar.view add command -label "Edit view..." -command editview \
545 .bar.view add command -label "Delete view" -command delview -state disabled
546 .bar.view add separator
547 .bar.view add radiobutton -label "All files" -command {showview 0} \
548 -variable selectedview -value 0
551 .bar add cascade -label "Help" -menu .bar.help
552 .bar.help add command -label "About gitk" -command about
553 .bar.help add command -label "Key bindings" -command keys
554 .bar.help configure -font $uifont
555 . configure -menu .bar
557 # the gui has upper and lower half, parts of a paned window.
558 panedwindow .ctop -orient vertical
560 # possibly use assumed geometry
561 if {![info exists geometry(pwsash0)]} {
562 set geometry(topheight) [expr {15 * $linespc}]
563 set geometry(topwidth) [expr {80 * $charspc}]
564 set geometry(botheight) [expr {15 * $linespc}]
565 set geometry(botwidth) [expr {50 * $charspc}]
566 set geometry(pwsash0) "[expr {40 * $charspc}] 2"
567 set geometry(pwsash1) "[expr {60 * $charspc}] 2"
570 # the upper half will have a paned window, a scroll bar to the right, and some stuff below
571 frame .tf -height $geometry(topheight) -width $geometry(topwidth)
573 panedwindow .tf.histframe.pwclist -orient horizontal -sashpad 0 -handlesize 4
575 # create three canvases
576 set cscroll .tf.histframe.csb
577 set canv .tf.histframe.pwclist.canv
579 -selectbackground $selectbgcolor \
580 -background $bgcolor -bd 0 \
581 -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll"
582 .tf.histframe.pwclist add $canv
583 set canv2 .tf.histframe.pwclist.canv2
585 -selectbackground $selectbgcolor \
586 -background $bgcolor -bd 0 -yscrollincr $linespc
587 .tf.histframe.pwclist add $canv2
588 set canv3 .tf.histframe.pwclist.canv3
590 -selectbackground $selectbgcolor \
591 -background $bgcolor -bd 0 -yscrollincr $linespc
592 .tf.histframe.pwclist add $canv3
593 eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0)
594 eval .tf.histframe.pwclist sash place 1 $geometry(pwsash1)
596 # a scroll bar to rule them
597 scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
598 pack $cscroll -side right -fill y
599 bind .tf.histframe.pwclist <Configure> {resizeclistpanes %W %w}
600 lappend bglist $canv $canv2 $canv3
601 pack .tf.histframe.pwclist -fill both -expand 1 -side left
603 # we have two button bars at bottom of top frame. Bar 1
605 frame .tf.lbar -height 15
607 set sha1entry .tf.bar.sha1
608 set entries $sha1entry
609 set sha1but .tf.bar.sha1label
610 button $sha1but -text "SHA1 ID: " -state disabled -relief flat \
611 -command gotocommit -width 8 -font $uifont
612 $sha1but conf -disabledforeground [$sha1but cget -foreground]
613 pack .tf.bar.sha1label -side left
614 entry $sha1entry -width 40 -font $textfont -textvariable sha1string
615 trace add variable sha1string write sha1change
616 pack $sha1entry -side left -pady 2
618 image create bitmap bm-left -data {
619 #define left_width 16
620 #define left_height 16
621 static unsigned char left_bits[] = {
622 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
623 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
624 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
626 image create bitmap bm-right -data {
627 #define right_width 16
628 #define right_height 16
629 static unsigned char right_bits[] = {
630 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
631 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
632 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
634 button .tf.bar.leftbut -image bm-left -command goback \
635 -state disabled -width 26
636 pack .tf.bar.leftbut -side left -fill y
637 button .tf.bar.rightbut -image bm-right -command goforw \
638 -state disabled -width 26
639 pack .tf.bar.rightbut -side left -fill y
641 button .tf.bar.findbut -text "Find" -command dofind -font $uifont
642 pack .tf.bar.findbut -side left
644 set fstring .tf.bar.findstring
645 lappend entries $fstring
646 entry $fstring -width 30 -font $textfont -textvariable findstring
647 trace add variable findstring write find_change
648 pack $fstring -side left -expand 1 -fill x -in .tf.bar
650 set findtypemenu [tk_optionMenu .tf.bar.findtype \
651 findtype Exact IgnCase Regexp]
652 trace add variable findtype write find_change
653 .tf.bar.findtype configure -font $uifont
654 .tf.bar.findtype.menu configure -font $uifont
655 set findloc "All fields"
656 tk_optionMenu .tf.bar.findloc findloc "All fields" Headline \
657 Comments Author Committer
658 trace add variable findloc write find_change
659 .tf.bar.findloc configure -font $uifont
660 .tf.bar.findloc.menu configure -font $uifont
661 pack .tf.bar.findloc -side right
662 pack .tf.bar.findtype -side right
664 # build up the bottom bar of upper window
665 label .tf.lbar.flabel -text "Highlight: Commits " \
667 pack .tf.lbar.flabel -side left -fill y
668 set gdttype "touching paths:"
669 set gm [tk_optionMenu .tf.lbar.gdttype gdttype "touching paths:" \
670 "adding/removing string:"]
671 trace add variable gdttype write hfiles_change
672 $gm conf -font $uifont
673 .tf.lbar.gdttype conf -font $uifont
674 pack .tf.lbar.gdttype -side left -fill y
675 entry .tf.lbar.fent -width 25 -font $textfont \
676 -textvariable highlight_files
677 trace add variable highlight_files write hfiles_change
678 lappend entries .tf.lbar.fent
679 pack .tf.lbar.fent -side left -fill x -expand 1
680 label .tf.lbar.vlabel -text " OR in view" -font $uifont
681 pack .tf.lbar.vlabel -side left -fill y
682 global viewhlmenu selectedhlview
683 set viewhlmenu [tk_optionMenu .tf.lbar.vhl selectedhlview None]
684 $viewhlmenu entryconf None -command delvhighlight
685 $viewhlmenu conf -font $uifont
686 .tf.lbar.vhl conf -font $uifont
687 pack .tf.lbar.vhl -side left -fill y
688 label .tf.lbar.rlabel -text " OR " -font $uifont
689 pack .tf.lbar.rlabel -side left -fill y
690 global highlight_related
691 set m [tk_optionMenu .tf.lbar.relm highlight_related None \
692 "Descendent" "Not descendent" "Ancestor" "Not ancestor"]
693 $m conf -font $uifont
694 .tf.lbar.relm conf -font $uifont
695 trace add variable highlight_related write vrel_change
696 pack .tf.lbar.relm -side left -fill y
698 # Finish putting the upper half of the viewer together
699 pack .tf.lbar -in .tf -side bottom -fill x
700 pack .tf.bar -in .tf -side bottom -fill x
701 pack .tf.histframe -fill both -side top -expand 1
703 .ctop paneconfigure .tf -height $geometry(topheight)
704 .ctop paneconfigure .tf -width $geometry(topwidth)
706 # now build up the bottom
707 panedwindow .pwbottom -orient horizontal
709 # lower left, a text box over search bar, scroll bar to the right
710 # if we know window height, then that will set the lower text height, otherwise
711 # we set lower text height which will drive window height
712 if {[info exists geometry(main)]} {
713 frame .bleft -width $geometry(botwidth)
715 frame .bleft -width $geometry(botwidth) -height $geometry(botheight)
720 button .bleft.top.search -text "Search" -command dosearch \
722 pack .bleft.top.search -side left -padx 5
723 set sstring .bleft.top.sstring
724 entry $sstring -width 20 -font $textfont -textvariable searchstring
725 lappend entries $sstring
726 trace add variable searchstring write incrsearch
727 pack $sstring -side left -expand 1 -fill x
728 radiobutton .bleft.mid.diff -text "Diff" \
729 -command changediffdisp -variable diffelide -value {0 0}
730 radiobutton .bleft.mid.old -text "Old version" \
731 -command changediffdisp -variable diffelide -value {0 1}
732 radiobutton .bleft.mid.new -text "New version" \
733 -command changediffdisp -variable diffelide -value {1 0}
734 pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left
735 set ctext .bleft.ctext
736 text $ctext -background $bgcolor -foreground $fgcolor \
737 -tabs "[expr {$tabstop * $charspc}]" \
738 -state disabled -font $textfont \
739 -yscrollcommand scrolltext -wrap none
740 scrollbar .bleft.sb -command "$ctext yview"
741 pack .bleft.top -side top -fill x
742 pack .bleft.mid -side top -fill x
743 pack .bleft.sb -side right -fill y
744 pack $ctext -side left -fill both -expand 1
745 lappend bglist $ctext
746 lappend fglist $ctext
748 $ctext tag conf comment -wrap $wrapcomment
749 $ctext tag conf filesep -font [concat $textfont bold] -back "#aaaaaa"
750 $ctext tag conf hunksep -fore [lindex $diffcolors 2]
751 $ctext tag conf d0 -fore [lindex $diffcolors 0]
752 $ctext tag conf d1 -fore [lindex $diffcolors 1]
753 $ctext tag conf m0 -fore red
754 $ctext tag conf m1 -fore blue
755 $ctext tag conf m2 -fore green
756 $ctext tag conf m3 -fore purple
757 $ctext tag conf m4 -fore brown
758 $ctext tag conf m5 -fore "#009090"
759 $ctext tag conf m6 -fore magenta
760 $ctext tag conf m7 -fore "#808000"
761 $ctext tag conf m8 -fore "#009000"
762 $ctext tag conf m9 -fore "#ff0080"
763 $ctext tag conf m10 -fore cyan
764 $ctext tag conf m11 -fore "#b07070"
765 $ctext tag conf m12 -fore "#70b0f0"
766 $ctext tag conf m13 -fore "#70f0b0"
767 $ctext tag conf m14 -fore "#f0b070"
768 $ctext tag conf m15 -fore "#ff70b0"
769 $ctext tag conf mmax -fore darkgrey
771 $ctext tag conf mresult -font [concat $textfont bold]
772 $ctext tag conf msep -font [concat $textfont bold]
773 $ctext tag conf found -back yellow
776 .pwbottom paneconfigure .bleft -width $geometry(botwidth)
781 radiobutton .bright.mode.patch -text "Patch" \
782 -command reselectline -variable cmitmode -value "patch"
783 .bright.mode.patch configure -font $uifont
784 radiobutton .bright.mode.tree -text "Tree" \
785 -command reselectline -variable cmitmode -value "tree"
786 .bright.mode.tree configure -font $uifont
787 grid .bright.mode.patch .bright.mode.tree -sticky ew
788 pack .bright.mode -side top -fill x
789 set cflist .bright.cfiles
790 set indent [font measure $mainfont "nn"]
792 -selectbackground $selectbgcolor \
793 -background $bgcolor -foreground $fgcolor \
795 -tabs [list $indent [expr {2 * $indent}]] \
796 -yscrollcommand ".bright.sb set" \
797 -cursor [. cget -cursor] \
798 -spacing1 1 -spacing3 1
799 lappend bglist $cflist
800 lappend fglist $cflist
801 scrollbar .bright.sb -command "$cflist yview"
802 pack .bright.sb -side right -fill y
803 pack $cflist -side left -fill both -expand 1
804 $cflist tag configure highlight \
805 -background [$cflist cget -selectbackground]
806 $cflist tag configure bold -font [concat $mainfont bold]
808 .pwbottom add .bright
811 # restore window position if known
812 if {[info exists geometry(main)]} {
813 wm geometry . "$geometry(main)"
816 if {[tk windowingsystem] eq {aqua}} {
822 bind .pwbottom <Configure> {resizecdetpanes %W %w}
823 pack .ctop -fill both -expand 1
824 bindall <1> {selcanvline %W %x %y}
825 #bindall <B1-Motion> {selcanvline %W %x %y}
826 if {[tk windowingsystem] == "win32"} {
827 bind . <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D }
828 bind $ctext <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D ; break }
830 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
831 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
833 bindall <2> "canvscan mark %W %x %y"
834 bindall <B2-Motion> "canvscan dragto %W %x %y"
835 bindkey <Home> selfirstline
836 bindkey <End> sellastline
837 bind . <Key-Up> "selnextline -1"
838 bind . <Key-Down> "selnextline 1"
839 bind . <Shift-Key-Up> "next_highlight -1"
840 bind . <Shift-Key-Down> "next_highlight 1"
841 bindkey <Key-Right> "goforw"
842 bindkey <Key-Left> "goback"
843 bind . <Key-Prior> "selnextpage -1"
844 bind . <Key-Next> "selnextpage 1"
845 bind . <$M1B-Home> "allcanvs yview moveto 0.0"
846 bind . <$M1B-End> "allcanvs yview moveto 1.0"
847 bind . <$M1B-Key-Up> "allcanvs yview scroll -1 units"
848 bind . <$M1B-Key-Down> "allcanvs yview scroll 1 units"
849 bind . <$M1B-Key-Prior> "allcanvs yview scroll -1 pages"
850 bind . <$M1B-Key-Next> "allcanvs yview scroll 1 pages"
851 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
852 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
853 bindkey <Key-space> "$ctext yview scroll 1 pages"
854 bindkey p "selnextline -1"
855 bindkey n "selnextline 1"
858 bindkey i "selnextline -1"
859 bindkey k "selnextline 1"
862 bindkey b "$ctext yview scroll -1 pages"
863 bindkey d "$ctext yview scroll 18 units"
864 bindkey u "$ctext yview scroll -18 units"
865 bindkey / {findnext 1}
866 bindkey <Key-Return> {findnext 0}
869 bindkey <F5> updatecommits
870 bind . <$M1B-q> doquit
871 bind . <$M1B-f> dofind
872 bind . <$M1B-g> {findnext 0}
873 bind . <$M1B-r> dosearchback
874 bind . <$M1B-s> dosearch
875 bind . <$M1B-equal> {incrfont 1}
876 bind . <$M1B-KP_Add> {incrfont 1}
877 bind . <$M1B-minus> {incrfont -1}
878 bind . <$M1B-KP_Subtract> {incrfont -1}
879 wm protocol . WM_DELETE_WINDOW doquit
880 bind . <Button-1> "click %W"
881 bind $fstring <Key-Return> dofind
882 bind $sha1entry <Key-Return> gotocommit
883 bind $sha1entry <<PasteSelection>> clearsha1
884 bind $cflist <1> {sel_flist %W %x %y; break}
885 bind $cflist <B1-Motion> {sel_flist %W %x %y; break}
886 bind $cflist <ButtonRelease-1> {treeclick %W %x %y}
887 bind $cflist <Button-3> {pop_flist_menu %W %X %Y %x %y}
889 set maincursor [. cget -cursor]
890 set textcursor [$ctext cget -cursor]
891 set curtextcursor $textcursor
893 set rowctxmenu .rowctxmenu
894 menu $rowctxmenu -tearoff 0
895 $rowctxmenu add command -label "Diff this -> selected" \
896 -command {diffvssel 0}
897 $rowctxmenu add command -label "Diff selected -> this" \
898 -command {diffvssel 1}
899 $rowctxmenu add command -label "Make patch" -command mkpatch
900 $rowctxmenu add command -label "Create tag" -command mktag
901 $rowctxmenu add command -label "Write commit to file" -command writecommit
902 $rowctxmenu add command -label "Create new branch" -command mkbranch
903 $rowctxmenu add command -label "Cherry-pick this commit" \
905 $rowctxmenu add command -label "Reset HEAD branch to here" \
908 set fakerowmenu .fakerowmenu
909 menu $fakerowmenu -tearoff 0
910 $fakerowmenu add command -label "Diff this -> selected" \
911 -command {diffvssel 0}
912 $fakerowmenu add command -label "Diff selected -> this" \
913 -command {diffvssel 1}
914 $fakerowmenu add command -label "Make patch" -command mkpatch
915 # $fakerowmenu add command -label "Commit" -command {mkcommit 0}
916 # $fakerowmenu add command -label "Commit all" -command {mkcommit 1}
917 # $fakerowmenu add command -label "Revert local changes" -command revertlocal
919 set headctxmenu .headctxmenu
920 menu $headctxmenu -tearoff 0
921 $headctxmenu add command -label "Check out this branch" \
923 $headctxmenu add command -label "Remove this branch" \
927 set flist_menu .flistctxmenu
928 menu $flist_menu -tearoff 0
929 $flist_menu add command -label "Highlight this too" \
930 -command {flist_hl 0}
931 $flist_menu add command -label "Highlight this only" \
932 -command {flist_hl 1}
935 # Windows sends all mouse wheel events to the current focused window, not
936 # the one where the mouse hovers, so bind those events here and redirect
937 # to the correct window
938 proc windows_mousewheel_redirector {W X Y D} {
939 global canv canv2 canv3
940 set w [winfo containing -displayof $W $X $Y]
942 set u [expr {$D < 0 ? 5 : -5}]
943 if {$w == $canv || $w == $canv2 || $w == $canv3} {
944 allcanvs yview scroll $u units
947 $w yview scroll $u units
953 # mouse-2 makes all windows scan vertically, but only the one
954 # the cursor is in scans horizontally
955 proc canvscan {op w x y} {
956 global canv canv2 canv3
957 foreach c [list $canv $canv2 $canv3] {
966 proc scrollcanv {cscroll f0 f1} {
972 # when we make a key binding for the toplevel, make sure
973 # it doesn't get triggered when that key is pressed in the
974 # find string entry widget.
975 proc bindkey {ev script} {
978 set escript [bind Entry $ev]
979 if {$escript == {}} {
980 set escript [bind Entry <Key>]
983 bind $e $ev "$escript; break"
987 # set the focus back to the toplevel for any click outside
991 foreach e [concat $entries $ctext] {
998 global canv canv2 canv3 ctext cflist mainfont textfont uifont tabstop
999 global stuffsaved findmergefiles maxgraphpct
1000 global maxwidth showneartags showlocalchanges
1001 global viewname viewfiles viewargs viewperm nextviewnum
1002 global cmitmode wrapcomment
1003 global colors bgcolor fgcolor diffcolors selectbgcolor
1005 if {$stuffsaved} return
1006 if {![winfo viewable .]} return
1008 set f [open "~/.gitk-new" w]
1009 puts $f [list set mainfont $mainfont]
1010 puts $f [list set textfont $textfont]
1011 puts $f [list set uifont $uifont]
1012 puts $f [list set tabstop $tabstop]
1013 puts $f [list set findmergefiles $findmergefiles]
1014 puts $f [list set maxgraphpct $maxgraphpct]
1015 puts $f [list set maxwidth $maxwidth]
1016 puts $f [list set cmitmode $cmitmode]
1017 puts $f [list set wrapcomment $wrapcomment]
1018 puts $f [list set showneartags $showneartags]
1019 puts $f [list set showlocalchanges $showlocalchanges]
1020 puts $f [list set bgcolor $bgcolor]
1021 puts $f [list set fgcolor $fgcolor]
1022 puts $f [list set colors $colors]
1023 puts $f [list set diffcolors $diffcolors]
1024 puts $f [list set selectbgcolor $selectbgcolor]
1026 puts $f "set geometry(main) [wm geometry .]"
1027 puts $f "set geometry(topwidth) [winfo width .tf]"
1028 puts $f "set geometry(topheight) [winfo height .tf]"
1029 puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\""
1030 puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sash coord 1]\""
1031 puts $f "set geometry(botwidth) [winfo width .bleft]"
1032 puts $f "set geometry(botheight) [winfo height .bleft]"
1034 puts -nonewline $f "set permviews {"
1035 for {set v 0} {$v < $nextviewnum} {incr v} {
1036 if {$viewperm($v)} {
1037 puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v)]}"
1042 file rename -force "~/.gitk-new" "~/.gitk"
1047 proc resizeclistpanes {win w} {
1049 if {[info exists oldwidth($win)]} {
1050 set s0 [$win sash coord 0]
1051 set s1 [$win sash coord 1]
1053 set sash0 [expr {int($w/2 - 2)}]
1054 set sash1 [expr {int($w*5/6 - 2)}]
1056 set factor [expr {1.0 * $w / $oldwidth($win)}]
1057 set sash0 [expr {int($factor * [lindex $s0 0])}]
1058 set sash1 [expr {int($factor * [lindex $s1 0])}]
1062 if {$sash1 < $sash0 + 20} {
1063 set sash1 [expr {$sash0 + 20}]
1065 if {$sash1 > $w - 10} {
1066 set sash1 [expr {$w - 10}]
1067 if {$sash0 > $sash1 - 20} {
1068 set sash0 [expr {$sash1 - 20}]
1072 $win sash place 0 $sash0 [lindex $s0 1]
1073 $win sash place 1 $sash1 [lindex $s1 1]
1075 set oldwidth($win) $w
1078 proc resizecdetpanes {win w} {
1080 if {[info exists oldwidth($win)]} {
1081 set s0 [$win sash coord 0]
1083 set sash0 [expr {int($w*3/4 - 2)}]
1085 set factor [expr {1.0 * $w / $oldwidth($win)}]
1086 set sash0 [expr {int($factor * [lindex $s0 0])}]
1090 if {$sash0 > $w - 15} {
1091 set sash0 [expr {$w - 15}]
1094 $win sash place 0 $sash0 [lindex $s0 1]
1096 set oldwidth($win) $w
1099 proc allcanvs args {
1100 global canv canv2 canv3
1106 proc bindall {event action} {
1107 global canv canv2 canv3
1108 bind $canv $event $action
1109 bind $canv2 $event $action
1110 bind $canv3 $event $action
1116 if {[winfo exists $w]} {
1121 wm title $w "About gitk"
1122 message $w.m -text {
1123 Gitk - a commit viewer for git
1125 Copyright © 2005-2006 Paul Mackerras
1127 Use and redistribute under the terms of the GNU General Public License} \
1128 -justify center -aspect 400 -border 2 -bg white -relief groove
1129 pack $w.m -side top -fill x -padx 2 -pady 2
1130 $w.m configure -font $uifont
1131 button $w.ok -text Close -command "destroy $w" -default active
1132 pack $w.ok -side bottom
1133 $w.ok configure -font $uifont
1134 bind $w <Visibility> "focus $w.ok"
1135 bind $w <Key-Escape> "destroy $w"
1136 bind $w <Key-Return> "destroy $w"
1142 if {[winfo exists $w]} {
1146 if {[tk windowingsystem] eq {aqua}} {
1152 wm title $w "Gitk key bindings"
1153 message $w.m -text "
1157 <Home> Move to first commit
1158 <End> Move to last commit
1159 <Up>, p, i Move up one commit
1160 <Down>, n, k Move down one commit
1161 <Left>, z, j Go back in history list
1162 <Right>, x, l Go forward in history list
1163 <PageUp> Move up one page in commit list
1164 <PageDown> Move down one page in commit list
1165 <$M1T-Home> Scroll to top of commit list
1166 <$M1T-End> Scroll to bottom of commit list
1167 <$M1T-Up> Scroll commit list up one line
1168 <$M1T-Down> Scroll commit list down one line
1169 <$M1T-PageUp> Scroll commit list up one page
1170 <$M1T-PageDown> Scroll commit list down one page
1171 <Shift-Up> Move to previous highlighted line
1172 <Shift-Down> Move to next highlighted line
1173 <Delete>, b Scroll diff view up one page
1174 <Backspace> Scroll diff view up one page
1175 <Space> Scroll diff view down one page
1176 u Scroll diff view up 18 lines
1177 d Scroll diff view down 18 lines
1179 <$M1T-G> Move to next find hit
1180 <Return> Move to next find hit
1181 / Move to next find hit, or redo find
1182 ? Move to previous find hit
1183 f Scroll diff view to next file
1184 <$M1T-S> Search for next hit in diff view
1185 <$M1T-R> Search for previous hit in diff view
1186 <$M1T-KP+> Increase font size
1187 <$M1T-plus> Increase font size
1188 <$M1T-KP-> Decrease font size
1189 <$M1T-minus> Decrease font size
1192 -justify left -bg white -border 2 -relief groove
1193 pack $w.m -side top -fill both -padx 2 -pady 2
1194 $w.m configure -font $uifont
1195 button $w.ok -text Close -command "destroy $w" -default active
1196 pack $w.ok -side bottom
1197 $w.ok configure -font $uifont
1198 bind $w <Visibility> "focus $w.ok"
1199 bind $w <Key-Escape> "destroy $w"
1200 bind $w <Key-Return> "destroy $w"
1203 # Procedures for manipulating the file list window at the
1204 # bottom right of the overall window.
1206 proc treeview {w l openlevs} {
1207 global treecontents treediropen treeheight treeparent treeindex
1217 set treecontents() {}
1218 $w conf -state normal
1220 while {[string range $f 0 $prefixend] ne $prefix} {
1221 if {$lev <= $openlevs} {
1222 $w mark set e:$treeindex($prefix) "end -1c"
1223 $w mark gravity e:$treeindex($prefix) left
1225 set treeheight($prefix) $ht
1226 incr ht [lindex $htstack end]
1227 set htstack [lreplace $htstack end end]
1228 set prefixend [lindex $prefendstack end]
1229 set prefendstack [lreplace $prefendstack end end]
1230 set prefix [string range $prefix 0 $prefixend]
1233 set tail [string range $f [expr {$prefixend+1}] end]
1234 while {[set slash [string first "/" $tail]] >= 0} {
1237 lappend prefendstack $prefixend
1238 incr prefixend [expr {$slash + 1}]
1239 set d [string range $tail 0 $slash]
1240 lappend treecontents($prefix) $d
1241 set oldprefix $prefix
1243 set treecontents($prefix) {}
1244 set treeindex($prefix) [incr ix]
1245 set treeparent($prefix) $oldprefix
1246 set tail [string range $tail [expr {$slash+1}] end]
1247 if {$lev <= $openlevs} {
1249 set treediropen($prefix) [expr {$lev < $openlevs}]
1250 set bm [expr {$lev == $openlevs? "tri-rt": "tri-dn"}]
1251 $w mark set d:$ix "end -1c"
1252 $w mark gravity d:$ix left
1254 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
1256 $w image create end -align center -image $bm -padx 1 \
1258 $w insert end $d [highlight_tag $prefix]
1259 $w mark set s:$ix "end -1c"
1260 $w mark gravity s:$ix left
1265 if {$lev <= $openlevs} {
1268 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
1270 $w insert end $tail [highlight_tag $f]
1272 lappend treecontents($prefix) $tail
1275 while {$htstack ne {}} {
1276 set treeheight($prefix) $ht
1277 incr ht [lindex $htstack end]
1278 set htstack [lreplace $htstack end end]
1279 set prefixend [lindex $prefendstack end]
1280 set prefendstack [lreplace $prefendstack end end]
1281 set prefix [string range $prefix 0 $prefixend]
1283 $w conf -state disabled
1286 proc linetoelt {l} {
1287 global treeheight treecontents
1292 foreach e $treecontents($prefix) {
1297 if {[string index $e end] eq "/"} {
1298 set n $treeheight($prefix$e)
1310 proc highlight_tree {y prefix} {
1311 global treeheight treecontents cflist
1313 foreach e $treecontents($prefix) {
1315 if {[highlight_tag $path] ne {}} {
1316 $cflist tag add bold $y.0 "$y.0 lineend"
1319 if {[string index $e end] eq "/" && $treeheight($path) > 1} {
1320 set y [highlight_tree $y $path]
1326 proc treeclosedir {w dir} {
1327 global treediropen treeheight treeparent treeindex
1329 set ix $treeindex($dir)
1330 $w conf -state normal
1331 $w delete s:$ix e:$ix
1332 set treediropen($dir) 0
1333 $w image configure a:$ix -image tri-rt
1334 $w conf -state disabled
1335 set n [expr {1 - $treeheight($dir)}]
1336 while {$dir ne {}} {
1337 incr treeheight($dir) $n
1338 set dir $treeparent($dir)
1342 proc treeopendir {w dir} {
1343 global treediropen treeheight treeparent treecontents treeindex
1345 set ix $treeindex($dir)
1346 $w conf -state normal
1347 $w image configure a:$ix -image tri-dn
1348 $w mark set e:$ix s:$ix
1349 $w mark gravity e:$ix right
1352 set n [llength $treecontents($dir)]
1353 for {set x $dir} {$x ne {}} {set x $treeparent($x)} {
1356 incr treeheight($x) $n
1358 foreach e $treecontents($dir) {
1360 if {[string index $e end] eq "/"} {
1361 set iy $treeindex($de)
1362 $w mark set d:$iy e:$ix
1363 $w mark gravity d:$iy left
1364 $w insert e:$ix $str
1365 set treediropen($de) 0
1366 $w image create e:$ix -align center -image tri-rt -padx 1 \
1368 $w insert e:$ix $e [highlight_tag $de]
1369 $w mark set s:$iy e:$ix
1370 $w mark gravity s:$iy left
1371 set treeheight($de) 1
1373 $w insert e:$ix $str
1374 $w insert e:$ix $e [highlight_tag $de]
1377 $w mark gravity e:$ix left
1378 $w conf -state disabled
1379 set treediropen($dir) 1
1380 set top [lindex [split [$w index @0,0] .] 0]
1381 set ht [$w cget -height]
1382 set l [lindex [split [$w index s:$ix] .] 0]
1385 } elseif {$l + $n + 1 > $top + $ht} {
1386 set top [expr {$l + $n + 2 - $ht}]
1394 proc treeclick {w x y} {
1395 global treediropen cmitmode ctext cflist cflist_top
1397 if {$cmitmode ne "tree"} return
1398 if {![info exists cflist_top]} return
1399 set l [lindex [split [$w index "@$x,$y"] "."] 0]
1400 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
1401 $cflist tag add highlight $l.0 "$l.0 lineend"
1407 set e [linetoelt $l]
1408 if {[string index $e end] ne "/"} {
1410 } elseif {$treediropen($e)} {
1417 proc setfilelist {id} {
1418 global treefilelist cflist
1420 treeview $cflist $treefilelist($id) 0
1423 image create bitmap tri-rt -background black -foreground blue -data {
1424 #define tri-rt_width 13
1425 #define tri-rt_height 13
1426 static unsigned char tri-rt_bits[] = {
1427 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x70, 0x00, 0xf0, 0x00,
1428 0xf0, 0x01, 0xf0, 0x00, 0x70, 0x00, 0x30, 0x00, 0x10, 0x00, 0x00, 0x00,
1431 #define tri-rt-mask_width 13
1432 #define tri-rt-mask_height 13
1433 static unsigned char tri-rt-mask_bits[] = {
1434 0x08, 0x00, 0x18, 0x00, 0x38, 0x00, 0x78, 0x00, 0xf8, 0x00, 0xf8, 0x01,
1435 0xf8, 0x03, 0xf8, 0x01, 0xf8, 0x00, 0x78, 0x00, 0x38, 0x00, 0x18, 0x00,
1438 image create bitmap tri-dn -background black -foreground blue -data {
1439 #define tri-dn_width 13
1440 #define tri-dn_height 13
1441 static unsigned char tri-dn_bits[] = {
1442 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0xf8, 0x03,
1443 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1446 #define tri-dn-mask_width 13
1447 #define tri-dn-mask_height 13
1448 static unsigned char tri-dn-mask_bits[] = {
1449 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1f, 0xfe, 0x0f, 0xfc, 0x07,
1450 0xf8, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
1454 proc init_flist {first} {
1455 global cflist cflist_top selectedline difffilestart
1457 $cflist conf -state normal
1458 $cflist delete 0.0 end
1460 $cflist insert end $first
1462 $cflist tag add highlight 1.0 "1.0 lineend"
1464 catch {unset cflist_top}
1466 $cflist conf -state disabled
1467 set difffilestart {}
1470 proc highlight_tag {f} {
1471 global highlight_paths
1473 foreach p $highlight_paths {
1474 if {[string match $p $f]} {
1481 proc highlight_filelist {} {
1482 global cmitmode cflist
1484 $cflist conf -state normal
1485 if {$cmitmode ne "tree"} {
1486 set end [lindex [split [$cflist index end] .] 0]
1487 for {set l 2} {$l < $end} {incr l} {
1488 set line [$cflist get $l.0 "$l.0 lineend"]
1489 if {[highlight_tag $line] ne {}} {
1490 $cflist tag add bold $l.0 "$l.0 lineend"
1496 $cflist conf -state disabled
1499 proc unhighlight_filelist {} {
1502 $cflist conf -state normal
1503 $cflist tag remove bold 1.0 end
1504 $cflist conf -state disabled
1507 proc add_flist {fl} {
1510 $cflist conf -state normal
1512 $cflist insert end "\n"
1513 $cflist insert end $f [highlight_tag $f]
1515 $cflist conf -state disabled
1518 proc sel_flist {w x y} {
1519 global ctext difffilestart cflist cflist_top cmitmode
1521 if {$cmitmode eq "tree"} return
1522 if {![info exists cflist_top]} return
1523 set l [lindex [split [$w index "@$x,$y"] "."] 0]
1524 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
1525 $cflist tag add highlight $l.0 "$l.0 lineend"
1530 catch {$ctext yview [lindex $difffilestart [expr {$l - 2}]]}
1534 proc pop_flist_menu {w X Y x y} {
1535 global ctext cflist cmitmode flist_menu flist_menu_file
1536 global treediffs diffids
1538 set l [lindex [split [$w index "@$x,$y"] "."] 0]
1540 if {$cmitmode eq "tree"} {
1541 set e [linetoelt $l]
1542 if {[string index $e end] eq "/"} return
1544 set e [lindex $treediffs($diffids) [expr {$l-2}]]
1546 set flist_menu_file $e
1547 tk_popup $flist_menu $X $Y
1550 proc flist_hl {only} {
1551 global flist_menu_file highlight_files
1553 set x [shellquote $flist_menu_file]
1554 if {$only || $highlight_files eq {}} {
1555 set highlight_files $x
1557 append highlight_files " " $x
1561 # Functions for adding and removing shell-type quoting
1563 proc shellquote {str} {
1564 if {![string match "*\['\"\\ \t]*" $str]} {
1567 if {![string match "*\['\"\\]*" $str]} {
1570 if {![string match "*'*" $str]} {
1573 return "\"[string map {\" \\\" \\ \\\\} $str]\""
1576 proc shellarglist {l} {
1582 append str [shellquote $a]
1587 proc shelldequote {str} {
1592 if {![regexp -start $used -indices "\['\"\\\\ \t]" $str first]} {
1593 append ret [string range $str $used end]
1594 set used [string length $str]
1597 set first [lindex $first 0]
1598 set ch [string index $str $first]
1599 if {$first > $used} {
1600 append ret [string range $str $used [expr {$first - 1}]]
1603 if {$ch eq " " || $ch eq "\t"} break
1606 set first [string first "'" $str $used]
1608 error "unmatched single-quote"
1610 append ret [string range $str $used [expr {$first - 1}]]
1615 if {$used >= [string length $str]} {
1616 error "trailing backslash"
1618 append ret [string index $str $used]
1623 if {![regexp -start $used -indices "\[\"\\\\]" $str first]} {
1624 error "unmatched double-quote"
1626 set first [lindex $first 0]
1627 set ch [string index $str $first]
1628 if {$first > $used} {
1629 append ret [string range $str $used [expr {$first - 1}]]
1632 if {$ch eq "\""} break
1634 append ret [string index $str $used]
1638 return [list $used $ret]
1641 proc shellsplit {str} {
1644 set str [string trimleft $str]
1645 if {$str eq {}} break
1646 set dq [shelldequote $str]
1647 set n [lindex $dq 0]
1648 set word [lindex $dq 1]
1649 set str [string range $str $n end]
1655 # Code to implement multiple views
1657 proc newview {ishighlight} {
1658 global nextviewnum newviewname newviewperm uifont newishighlight
1659 global newviewargs revtreeargs
1661 set newishighlight $ishighlight
1663 if {[winfo exists $top]} {
1667 set newviewname($nextviewnum) "View $nextviewnum"
1668 set newviewperm($nextviewnum) 0
1669 set newviewargs($nextviewnum) [shellarglist $revtreeargs]
1670 vieweditor $top $nextviewnum "Gitk view definition"
1675 global viewname viewperm newviewname newviewperm
1676 global viewargs newviewargs
1678 set top .gitkvedit-$curview
1679 if {[winfo exists $top]} {
1683 set newviewname($curview) $viewname($curview)
1684 set newviewperm($curview) $viewperm($curview)
1685 set newviewargs($curview) [shellarglist $viewargs($curview)]
1686 vieweditor $top $curview "Gitk: edit view $viewname($curview)"
1689 proc vieweditor {top n title} {
1690 global newviewname newviewperm viewfiles
1694 wm title $top $title
1695 label $top.nl -text "Name" -font $uifont
1696 entry $top.name -width 20 -textvariable newviewname($n) -font $uifont
1697 grid $top.nl $top.name -sticky w -pady 5
1698 checkbutton $top.perm -text "Remember this view" -variable newviewperm($n) \
1700 grid $top.perm - -pady 5 -sticky w
1701 message $top.al -aspect 1000 -font $uifont \
1702 -text "Commits to include (arguments to git rev-list):"
1703 grid $top.al - -sticky w -pady 5
1704 entry $top.args -width 50 -textvariable newviewargs($n) \
1705 -background white -font $uifont
1706 grid $top.args - -sticky ew -padx 5
1707 message $top.l -aspect 1000 -font $uifont \
1708 -text "Enter files and directories to include, one per line:"
1709 grid $top.l - -sticky w
1710 text $top.t -width 40 -height 10 -background white -font $uifont
1711 if {[info exists viewfiles($n)]} {
1712 foreach f $viewfiles($n) {
1713 $top.t insert end $f
1714 $top.t insert end "\n"
1716 $top.t delete {end - 1c} end
1717 $top.t mark set insert 0.0
1719 grid $top.t - -sticky ew -padx 5
1721 button $top.buts.ok -text "OK" -command [list newviewok $top $n] \
1723 button $top.buts.can -text "Cancel" -command [list destroy $top] \
1725 grid $top.buts.ok $top.buts.can
1726 grid columnconfigure $top.buts 0 -weight 1 -uniform a
1727 grid columnconfigure $top.buts 1 -weight 1 -uniform a
1728 grid $top.buts - -pady 10 -sticky ew
1732 proc doviewmenu {m first cmd op argv} {
1733 set nmenu [$m index end]
1734 for {set i $first} {$i <= $nmenu} {incr i} {
1735 if {[$m entrycget $i -command] eq $cmd} {
1736 eval $m $op $i $argv
1742 proc allviewmenus {n op args} {
1745 doviewmenu .bar.view 5 [list showview $n] $op $args
1746 doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
1749 proc newviewok {top n} {
1750 global nextviewnum newviewperm newviewname newishighlight
1751 global viewname viewfiles viewperm selectedview curview
1752 global viewargs newviewargs viewhlmenu
1755 set newargs [shellsplit $newviewargs($n)]
1757 error_popup "Error in commit selection arguments: $err"
1763 foreach f [split [$top.t get 0.0 end] "\n"] {
1764 set ft [string trim $f]
1769 if {![info exists viewfiles($n)]} {
1770 # creating a new view
1772 set viewname($n) $newviewname($n)
1773 set viewperm($n) $newviewperm($n)
1774 set viewfiles($n) $files
1775 set viewargs($n) $newargs
1777 if {!$newishighlight} {
1780 run addvhighlight $n
1783 # editing an existing view
1784 set viewperm($n) $newviewperm($n)
1785 if {$newviewname($n) ne $viewname($n)} {
1786 set viewname($n) $newviewname($n)
1787 doviewmenu .bar.view 5 [list showview $n] \
1788 entryconf [list -label $viewname($n)]
1789 doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
1790 entryconf [list -label $viewname($n) -value $viewname($n)]
1792 if {$files ne $viewfiles($n) || $newargs ne $viewargs($n)} {
1793 set viewfiles($n) $files
1794 set viewargs($n) $newargs
1795 if {$curview == $n} {
1800 catch {destroy $top}
1804 global curview viewdata viewperm hlview selectedhlview
1806 if {$curview == 0} return
1807 if {[info exists hlview] && $hlview == $curview} {
1808 set selectedhlview None
1811 allviewmenus $curview delete
1812 set viewdata($curview) {}
1813 set viewperm($curview) 0
1817 proc addviewmenu {n} {
1818 global viewname viewhlmenu
1820 .bar.view add radiobutton -label $viewname($n) \
1821 -command [list showview $n] -variable selectedview -value $n
1822 $viewhlmenu add radiobutton -label $viewname($n) \
1823 -command [list addvhighlight $n] -variable selectedhlview
1826 proc flatten {var} {
1830 foreach i [array names $var] {
1831 lappend ret $i [set $var\($i\)]
1836 proc unflatten {var l} {
1846 global curview viewdata viewfiles
1847 global displayorder parentlist rowidlist rowoffsets
1848 global colormap rowtextx commitrow nextcolor canvxmax
1849 global numcommits rowrangelist commitlisted idrowranges rowchk
1850 global selectedline currentid canv canvy0
1852 global pending_select phase
1853 global commitidx rowlaidout rowoptim
1855 global selectedview selectfirst
1856 global vparentlist vdisporder vcmitlisted
1857 global hlview selectedhlview
1859 if {$n == $curview} return
1861 if {[info exists selectedline]} {
1862 set selid $currentid
1863 set y [yc $selectedline]
1864 set ymax [lindex [$canv cget -scrollregion] 3]
1865 set span [$canv yview]
1866 set ytop [expr {[lindex $span 0] * $ymax}]
1867 set ybot [expr {[lindex $span 1] * $ymax}]
1868 if {$ytop < $y && $y < $ybot} {
1869 set yscreen [expr {$y - $ytop}]
1871 set yscreen [expr {($ybot - $ytop) / 2}]
1873 } elseif {[info exists pending_select]} {
1874 set selid $pending_select
1875 unset pending_select
1879 if {$curview >= 0} {
1880 set vparentlist($curview) $parentlist
1881 set vdisporder($curview) $displayorder
1882 set vcmitlisted($curview) $commitlisted
1884 set viewdata($curview) \
1885 [list $phase $rowidlist $rowoffsets $rowrangelist \
1886 [flatten idrowranges] [flatten idinlist] \
1887 $rowlaidout $rowoptim $numcommits]
1888 } elseif {![info exists viewdata($curview)]
1889 || [lindex $viewdata($curview) 0] ne {}} {
1890 set viewdata($curview) \
1891 [list {} $rowidlist $rowoffsets $rowrangelist]
1894 catch {unset treediffs}
1896 if {[info exists hlview] && $hlview == $n} {
1898 set selectedhlview None
1903 .bar.view entryconf Edit* -state [expr {$n == 0? "disabled": "normal"}]
1904 .bar.view entryconf Delete* -state [expr {$n == 0? "disabled": "normal"}]
1906 if {![info exists viewdata($n)]} {
1908 set pending_select $selid
1915 set phase [lindex $v 0]
1916 set displayorder $vdisporder($n)
1917 set parentlist $vparentlist($n)
1918 set commitlisted $vcmitlisted($n)
1919 set rowidlist [lindex $v 1]
1920 set rowoffsets [lindex $v 2]
1921 set rowrangelist [lindex $v 3]
1923 set numcommits [llength $displayorder]
1924 catch {unset idrowranges}
1926 unflatten idrowranges [lindex $v 4]
1927 unflatten idinlist [lindex $v 5]
1928 set rowlaidout [lindex $v 6]
1929 set rowoptim [lindex $v 7]
1930 set numcommits [lindex $v 8]
1931 catch {unset rowchk}
1934 catch {unset colormap}
1935 catch {unset rowtextx}
1937 set canvxmax [$canv cget -width]
1944 if {$selid ne {} && [info exists commitrow($n,$selid)]} {
1945 set row $commitrow($n,$selid)
1946 # try to get the selected row in the same position on the screen
1947 set ymax [lindex [$canv cget -scrollregion] 3]
1948 set ytop [expr {[yc $row] - $yscreen}]
1952 set yf [expr {$ytop * 1.0 / $ymax}]
1954 allcanvs yview moveto $yf
1958 } elseif {$selid ne {}} {
1959 set pending_select $selid
1961 set row [first_real_row]
1962 if {$row < $numcommits} {
1969 if {$phase eq "getcommits"} {
1970 show_status "Reading commits..."
1973 } elseif {$numcommits == 0} {
1974 show_status "No commits selected"
1978 # Stuff relating to the highlighting facility
1980 proc ishighlighted {row} {
1981 global vhighlights fhighlights nhighlights rhighlights
1983 if {[info exists nhighlights($row)] && $nhighlights($row) > 0} {
1984 return $nhighlights($row)
1986 if {[info exists vhighlights($row)] && $vhighlights($row) > 0} {
1987 return $vhighlights($row)
1989 if {[info exists fhighlights($row)] && $fhighlights($row) > 0} {
1990 return $fhighlights($row)
1992 if {[info exists rhighlights($row)] && $rhighlights($row) > 0} {
1993 return $rhighlights($row)
1998 proc bolden {row font} {
1999 global canv linehtag selectedline boldrows
2001 lappend boldrows $row
2002 $canv itemconf $linehtag($row) -font $font
2003 if {[info exists selectedline] && $row == $selectedline} {
2005 set t [eval $canv create rect [$canv bbox $linehtag($row)] \
2006 -outline {{}} -tags secsel \
2007 -fill [$canv cget -selectbackground]]
2012 proc bolden_name {row font} {
2013 global canv2 linentag selectedline boldnamerows
2015 lappend boldnamerows $row
2016 $canv2 itemconf $linentag($row) -font $font
2017 if {[info exists selectedline] && $row == $selectedline} {
2018 $canv2 delete secsel
2019 set t [eval $canv2 create rect [$canv2 bbox $linentag($row)] \
2020 -outline {{}} -tags secsel \
2021 -fill [$canv2 cget -selectbackground]]
2027 global mainfont boldrows
2030 foreach row $boldrows {
2031 if {![ishighlighted $row]} {
2032 bolden $row $mainfont
2034 lappend stillbold $row
2037 set boldrows $stillbold
2040 proc addvhighlight {n} {
2041 global hlview curview viewdata vhl_done vhighlights commitidx
2043 if {[info exists hlview]} {
2047 if {$n != $curview && ![info exists viewdata($n)]} {
2048 set viewdata($n) [list getcommits {{}} {{}} {} {} {} 0 0 0 {}]
2049 set vparentlist($n) {}
2050 set vdisporder($n) {}
2051 set vcmitlisted($n) {}
2054 set vhl_done $commitidx($hlview)
2055 if {$vhl_done > 0} {
2060 proc delvhighlight {} {
2061 global hlview vhighlights
2063 if {![info exists hlview]} return
2065 catch {unset vhighlights}
2069 proc vhighlightmore {} {
2070 global hlview vhl_done commitidx vhighlights
2071 global displayorder vdisporder curview mainfont
2073 set font [concat $mainfont bold]
2074 set max $commitidx($hlview)
2075 if {$hlview == $curview} {
2076 set disp $displayorder
2078 set disp $vdisporder($hlview)
2080 set vr [visiblerows]
2081 set r0 [lindex $vr 0]
2082 set r1 [lindex $vr 1]
2083 for {set i $vhl_done} {$i < $max} {incr i} {
2084 set id [lindex $disp $i]
2085 if {[info exists commitrow($curview,$id)]} {
2086 set row $commitrow($curview,$id)
2087 if {$r0 <= $row && $row <= $r1} {
2088 if {![highlighted $row]} {
2091 set vhighlights($row) 1
2098 proc askvhighlight {row id} {
2099 global hlview vhighlights commitrow iddrawn mainfont
2101 if {[info exists commitrow($hlview,$id)]} {
2102 if {[info exists iddrawn($id)] && ![ishighlighted $row]} {
2103 bolden $row [concat $mainfont bold]
2105 set vhighlights($row) 1
2107 set vhighlights($row) 0
2111 proc hfiles_change {name ix op} {
2112 global highlight_files filehighlight fhighlights fh_serial
2113 global mainfont highlight_paths
2115 if {[info exists filehighlight]} {
2116 # delete previous highlights
2117 catch {close $filehighlight}
2119 catch {unset fhighlights}
2121 unhighlight_filelist
2123 set highlight_paths {}
2124 after cancel do_file_hl $fh_serial
2126 if {$highlight_files ne {}} {
2127 after 300 do_file_hl $fh_serial
2131 proc makepatterns {l} {
2134 set ee [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} $e]
2135 if {[string index $ee end] eq "/"} {
2145 proc do_file_hl {serial} {
2146 global highlight_files filehighlight highlight_paths gdttype fhl_list
2148 if {$gdttype eq "touching paths:"} {
2149 if {[catch {set paths [shellsplit $highlight_files]}]} return
2150 set highlight_paths [makepatterns $paths]
2152 set gdtargs [concat -- $paths]
2154 set gdtargs [list "-S$highlight_files"]
2156 set cmd [concat | git diff-tree -r -s --stdin $gdtargs]
2157 set filehighlight [open $cmd r+]
2158 fconfigure $filehighlight -blocking 0
2159 filerun $filehighlight readfhighlight
2165 proc flushhighlights {} {
2166 global filehighlight fhl_list
2168 if {[info exists filehighlight]} {
2170 puts $filehighlight ""
2171 flush $filehighlight
2175 proc askfilehighlight {row id} {
2176 global filehighlight fhighlights fhl_list
2178 lappend fhl_list $id
2179 set fhighlights($row) -1
2180 puts $filehighlight $id
2183 proc readfhighlight {} {
2184 global filehighlight fhighlights commitrow curview mainfont iddrawn
2187 if {![info exists filehighlight]} {
2191 while {[incr nr] <= 100 && [gets $filehighlight line] >= 0} {
2192 set line [string trim $line]
2193 set i [lsearch -exact $fhl_list $line]
2194 if {$i < 0} continue
2195 for {set j 0} {$j < $i} {incr j} {
2196 set id [lindex $fhl_list $j]
2197 if {[info exists commitrow($curview,$id)]} {
2198 set fhighlights($commitrow($curview,$id)) 0
2201 set fhl_list [lrange $fhl_list [expr {$i+1}] end]
2202 if {$line eq {}} continue
2203 if {![info exists commitrow($curview,$line)]} continue
2204 set row $commitrow($curview,$line)
2205 if {[info exists iddrawn($line)] && ![ishighlighted $row]} {
2206 bolden $row [concat $mainfont bold]
2208 set fhighlights($row) 1
2210 if {[eof $filehighlight]} {
2212 puts "oops, git diff-tree died"
2213 catch {close $filehighlight}
2221 proc find_change {name ix op} {
2222 global nhighlights mainfont boldnamerows
2223 global findstring findpattern findtype
2225 # delete previous highlights, if any
2226 foreach row $boldnamerows {
2227 bolden_name $row $mainfont
2230 catch {unset nhighlights}
2233 if {$findtype ne "Regexp"} {
2234 set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \
2236 set findpattern "*$e*"
2241 proc doesmatch {f} {
2242 global findtype findstring findpattern
2244 if {$findtype eq "Regexp"} {
2245 return [regexp $findstring $f]
2246 } elseif {$findtype eq "IgnCase"} {
2247 return [string match -nocase $findpattern $f]
2249 return [string match $findpattern $f]
2253 proc askfindhighlight {row id} {
2254 global nhighlights commitinfo iddrawn mainfont
2256 global markingmatches
2258 if {![info exists commitinfo($id)]} {
2261 set info $commitinfo($id)
2263 set fldtypes {Headline Author Date Committer CDate Comments}
2264 foreach f $info ty $fldtypes {
2265 if {($findloc eq "All fields" || $findloc eq $ty) &&
2267 if {$ty eq "Author"} {
2274 if {$isbold && [info exists iddrawn($id)]} {
2275 set f [concat $mainfont bold]
2276 if {![ishighlighted $row]} {
2282 if {$markingmatches} {
2283 markrowmatches $row $id
2286 set nhighlights($row) $isbold
2289 proc markrowmatches {row id} {
2290 global canv canv2 linehtag linentag commitinfo findloc
2292 set headline [lindex $commitinfo($id) 0]
2293 set author [lindex $commitinfo($id) 1]
2294 $canv delete match$row
2295 $canv2 delete match$row
2296 if {$findloc eq "All fields" || $findloc eq "Headline"} {
2297 set m [findmatches $headline]
2299 markmatches $canv $row $headline $linehtag($row) $m \
2300 [$canv itemcget $linehtag($row) -font] $row
2303 if {$findloc eq "All fields" || $findloc eq "Author"} {
2304 set m [findmatches $author]
2306 markmatches $canv2 $row $author $linentag($row) $m \
2307 [$canv2 itemcget $linentag($row) -font] $row
2312 proc vrel_change {name ix op} {
2313 global highlight_related
2316 if {$highlight_related ne "None"} {
2321 # prepare for testing whether commits are descendents or ancestors of a
2322 proc rhighlight_sel {a} {
2323 global descendent desc_todo ancestor anc_todo
2324 global highlight_related rhighlights
2326 catch {unset descendent}
2327 set desc_todo [list $a]
2328 catch {unset ancestor}
2329 set anc_todo [list $a]
2330 if {$highlight_related ne "None"} {
2336 proc rhighlight_none {} {
2339 catch {unset rhighlights}
2343 proc is_descendent {a} {
2344 global curview children commitrow descendent desc_todo
2347 set la $commitrow($v,$a)
2351 for {set i 0} {$i < [llength $todo]} {incr i} {
2352 set do [lindex $todo $i]
2353 if {$commitrow($v,$do) < $la} {
2354 lappend leftover $do
2357 foreach nk $children($v,$do) {
2358 if {![info exists descendent($nk)]} {
2359 set descendent($nk) 1
2367 set desc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
2371 set descendent($a) 0
2372 set desc_todo $leftover
2375 proc is_ancestor {a} {
2376 global curview parentlist commitrow ancestor anc_todo
2379 set la $commitrow($v,$a)
2383 for {set i 0} {$i < [llength $todo]} {incr i} {
2384 set do [lindex $todo $i]
2385 if {![info exists commitrow($v,$do)] || $commitrow($v,$do) > $la} {
2386 lappend leftover $do
2389 foreach np [lindex $parentlist $commitrow($v,$do)] {
2390 if {![info exists ancestor($np)]} {
2399 set anc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
2404 set anc_todo $leftover
2407 proc askrelhighlight {row id} {
2408 global descendent highlight_related iddrawn mainfont rhighlights
2409 global selectedline ancestor
2411 if {![info exists selectedline]} return
2413 if {$highlight_related eq "Descendent" ||
2414 $highlight_related eq "Not descendent"} {
2415 if {![info exists descendent($id)]} {
2418 if {$descendent($id) == ($highlight_related eq "Descendent")} {
2421 } elseif {$highlight_related eq "Ancestor" ||
2422 $highlight_related eq "Not ancestor"} {
2423 if {![info exists ancestor($id)]} {
2426 if {$ancestor($id) == ($highlight_related eq "Ancestor")} {
2430 if {[info exists iddrawn($id)]} {
2431 if {$isbold && ![ishighlighted $row]} {
2432 bolden $row [concat $mainfont bold]
2435 set rhighlights($row) $isbold
2438 proc next_hlcont {} {
2439 global fhl_row fhl_dirn displayorder numcommits
2440 global vhighlights fhighlights nhighlights rhighlights
2441 global hlview filehighlight findstring highlight_related
2443 if {![info exists fhl_dirn] || $fhl_dirn == 0} return
2446 if {$row < 0 || $row >= $numcommits} {
2451 set id [lindex $displayorder $row]
2452 if {[info exists hlview]} {
2453 if {![info exists vhighlights($row)]} {
2454 askvhighlight $row $id
2456 if {$vhighlights($row) > 0} break
2458 if {$findstring ne {}} {
2459 if {![info exists nhighlights($row)]} {
2460 askfindhighlight $row $id
2462 if {$nhighlights($row) > 0} break
2464 if {$highlight_related ne "None"} {
2465 if {![info exists rhighlights($row)]} {
2466 askrelhighlight $row $id
2468 if {$rhighlights($row) > 0} break
2470 if {[info exists filehighlight]} {
2471 if {![info exists fhighlights($row)]} {
2472 # ask for a few more while we're at it...
2474 for {set n 0} {$n < 100} {incr n} {
2475 if {![info exists fhighlights($r)]} {
2476 askfilehighlight $r [lindex $displayorder $r]
2479 if {$r < 0 || $r >= $numcommits} break
2483 if {$fhighlights($row) < 0} {
2487 if {$fhighlights($row) > 0} break
2495 proc next_highlight {dirn} {
2496 global selectedline fhl_row fhl_dirn
2497 global hlview filehighlight findstring highlight_related
2499 if {![info exists selectedline]} return
2500 if {!([info exists hlview] || $findstring ne {} ||
2501 $highlight_related ne "None" || [info exists filehighlight])} return
2502 set fhl_row [expr {$selectedline + $dirn}]
2507 proc cancel_next_highlight {} {
2513 # Graph layout functions
2515 proc shortids {ids} {
2518 if {[llength $id] > 1} {
2519 lappend res [shortids $id]
2520 } elseif {[regexp {^[0-9a-f]{40}$} $id]} {
2521 lappend res [string range $id 0 7]
2529 proc incrange {l x o} {
2532 set e [lindex $l $x]
2534 lset l $x [expr {$e + $o}]
2543 for {} {$n > 0} {incr n -1} {
2549 proc usedinrange {id l1 l2} {
2550 global children commitrow curview
2552 if {[info exists commitrow($curview,$id)]} {
2553 set r $commitrow($curview,$id)
2554 if {$l1 <= $r && $r <= $l2} {
2555 return [expr {$r - $l1 + 1}]
2558 set kids $children($curview,$id)
2560 set r $commitrow($curview,$c)
2561 if {$l1 <= $r && $r <= $l2} {
2562 return [expr {$r - $l1 + 1}]
2568 proc sanity {row {full 0}} {
2569 global rowidlist rowoffsets
2572 set ids [lindex $rowidlist $row]
2575 if {$id eq {}} continue
2576 if {$col < [llength $ids] - 1 &&
2577 [lsearch -exact -start [expr {$col+1}] $ids $id] >= 0} {
2578 puts "oops: [shortids $id] repeated in row $row col $col: {[shortids [lindex $rowidlist $row]]}"
2580 set o [lindex $rowoffsets $row $col]
2586 if {[lindex $rowidlist $y $x] != $id} {
2587 puts "oops: rowoffsets wrong at row [expr {$y+1}] col [expr {$x-$o}]"
2588 puts " id=[shortids $id] check started at row $row"
2589 for {set i $row} {$i >= $y} {incr i -1} {
2590 puts " row $i ids={[shortids [lindex $rowidlist $i]]} offs={[lindex $rowoffsets $i]}"
2595 set o [lindex $rowoffsets $y $x]
2600 proc makeuparrow {oid x y z} {
2601 global rowidlist rowoffsets uparrowlen idrowranges displayorder
2603 for {set i 1} {$i < $uparrowlen && $y > 1} {incr i} {
2606 set off0 [lindex $rowoffsets $y]
2607 for {set x0 $x} {1} {incr x0} {
2608 if {$x0 >= [llength $off0]} {
2609 set x0 [llength [lindex $rowoffsets [expr {$y-1}]]]
2612 set z [lindex $off0 $x0]
2618 set z [expr {$x0 - $x}]
2619 lset rowidlist $y [linsert [lindex $rowidlist $y] $x $oid]
2620 lset rowoffsets $y [linsert [lindex $rowoffsets $y] $x $z]
2622 set tmp [lreplace [lindex $rowoffsets $y] $x $x {}]
2623 lset rowoffsets $y [incrange $tmp [expr {$x+1}] -1]
2624 lappend idrowranges($oid) [lindex $displayorder $y]
2627 proc initlayout {} {
2628 global rowidlist rowoffsets displayorder commitlisted
2629 global rowlaidout rowoptim
2630 global idinlist rowchk rowrangelist idrowranges
2631 global numcommits canvxmax canv
2634 global colormap rowtextx
2645 catch {unset idinlist}
2646 catch {unset rowchk}
2649 set canvxmax [$canv cget -width]
2650 catch {unset colormap}
2651 catch {unset rowtextx}
2652 catch {unset idrowranges}
2656 proc setcanvscroll {} {
2657 global canv canv2 canv3 numcommits linespc canvxmax canvy0
2659 set ymax [expr {$canvy0 + ($numcommits - 0.5) * $linespc + 2}]
2660 $canv conf -scrollregion [list 0 0 $canvxmax $ymax]
2661 $canv2 conf -scrollregion [list 0 0 0 $ymax]
2662 $canv3 conf -scrollregion [list 0 0 0 $ymax]
2665 proc visiblerows {} {
2666 global canv numcommits linespc
2668 set ymax [lindex [$canv cget -scrollregion] 3]
2669 if {$ymax eq {} || $ymax == 0} return
2671 set y0 [expr {int([lindex $f 0] * $ymax)}]
2672 set r0 [expr {int(($y0 - 3) / $linespc) - 1}]
2676 set y1 [expr {int([lindex $f 1] * $ymax)}]
2677 set r1 [expr {int(($y1 - 3) / $linespc) + 1}]
2678 if {$r1 >= $numcommits} {
2679 set r1 [expr {$numcommits - 1}]
2681 return [list $r0 $r1]
2684 proc layoutmore {tmax allread} {
2685 global rowlaidout rowoptim commitidx numcommits optim_delay
2686 global uparrowlen curview rowidlist idinlist
2689 set showdelay $optim_delay
2690 set optdelay [expr {$uparrowlen + 1}]
2692 if {$rowoptim - $showdelay > $numcommits} {
2693 showstuff [expr {$rowoptim - $showdelay}] $showlast
2694 } elseif {$rowlaidout - $optdelay > $rowoptim} {
2695 set nr [expr {$rowlaidout - $optdelay - $rowoptim}]
2699 optimize_rows $rowoptim 0 [expr {$rowoptim + $nr}]
2701 } elseif {$commitidx($curview) > $rowlaidout} {
2702 set nr [expr {$commitidx($curview) - $rowlaidout}]
2703 # may need to increase this threshold if uparrowlen or
2704 # mingaplen are increased...
2709 set rowlaidout [layoutrows $row [expr {$row + $nr}] $allread]
2710 if {$rowlaidout == $row} {
2713 } elseif {$allread} {
2715 set nrows $commitidx($curview)
2716 if {[lindex $rowidlist $nrows] ne {} ||
2717 [array names idinlist] ne {}} {
2719 set rowlaidout $commitidx($curview)
2720 } elseif {$rowoptim == $nrows} {
2723 if {$numcommits == $nrows} {
2730 if {$tmax ne {} && [clock clicks -milliseconds] >= $tmax} {
2736 proc showstuff {canshow last} {
2737 global numcommits commitrow pending_select selectedline curview
2738 global lookingforhead mainheadid displayorder selectfirst
2739 global lastscrollset
2741 if {$numcommits == 0} {
2743 set phase "incrdraw"
2747 set prev $numcommits
2748 set numcommits $canshow
2749 set t [clock clicks -milliseconds]
2750 if {$prev < 100 || $last || $t - $lastscrollset > 500} {
2751 set lastscrollset $t
2754 set rows [visiblerows]
2755 set r1 [lindex $rows 1]
2756 if {$r1 >= $canshow} {
2757 set r1 [expr {$canshow - 1}]
2762 if {[info exists pending_select] &&
2763 [info exists commitrow($curview,$pending_select)] &&
2764 $commitrow($curview,$pending_select) < $numcommits} {
2765 selectline $commitrow($curview,$pending_select) 1
2768 if {[info exists selectedline] || [info exists pending_select]} {
2771 set l [first_real_row]
2776 if {$lookingforhead && [info exists commitrow($curview,$mainheadid)]
2777 && ($last || $commitrow($curview,$mainheadid) < $numcommits - 1)} {
2778 set lookingforhead 0
2783 proc doshowlocalchanges {} {
2784 global lookingforhead curview mainheadid phase commitrow
2786 if {[info exists commitrow($curview,$mainheadid)] &&
2787 ($phase eq {} || $commitrow($curview,$mainheadid) < $numcommits - 1)} {
2789 } elseif {$phase ne {}} {
2790 set lookingforhead 1
2794 proc dohidelocalchanges {} {
2795 global lookingforhead localfrow localirow lserial
2797 set lookingforhead 0
2798 if {$localfrow >= 0} {
2799 removerow $localfrow
2801 if {$localirow > 0} {
2805 if {$localirow >= 0} {
2806 removerow $localirow
2812 # spawn off a process to do git diff-index --cached HEAD
2813 proc dodiffindex {} {
2814 global localirow localfrow lserial
2819 set fd [open "|git diff-index --cached HEAD" r]
2820 fconfigure $fd -blocking 0
2821 filerun $fd [list readdiffindex $fd $lserial]
2824 proc readdiffindex {fd serial} {
2825 global localirow commitrow mainheadid nullid2 curview
2826 global commitinfo commitdata lserial
2829 if {[gets $fd line] < 0} {
2835 # we only need to see one line and we don't really care what it says...
2838 # now see if there are any local changes not checked in to the index
2839 if {$serial == $lserial} {
2840 set fd [open "|git diff-files" r]
2841 fconfigure $fd -blocking 0
2842 filerun $fd [list readdifffiles $fd $serial]
2845 if {$isdiff && $serial == $lserial && $localirow == -1} {
2846 # add the line for the changes in the index to the graph
2847 set localirow $commitrow($curview,$mainheadid)
2848 set hl "Local changes checked in to index but not committed"
2849 set commitinfo($nullid2) [list $hl {} {} {} {} " $hl\n"]
2850 set commitdata($nullid2) "\n $hl\n"
2851 insertrow $localirow $nullid2
2856 proc readdifffiles {fd serial} {
2857 global localirow localfrow commitrow mainheadid nullid curview
2858 global commitinfo commitdata lserial
2861 if {[gets $fd line] < 0} {
2867 # we only need to see one line and we don't really care what it says...
2870 if {$isdiff && $serial == $lserial && $localfrow == -1} {
2871 # add the line for the local diff to the graph
2872 if {$localirow >= 0} {
2873 set localfrow $localirow
2876 set localfrow $commitrow($curview,$mainheadid)
2878 set hl "Local uncommitted changes, not checked in to index"
2879 set commitinfo($nullid) [list $hl {} {} {} {} " $hl\n"]
2880 set commitdata($nullid) "\n $hl\n"
2881 insertrow $localfrow $nullid
2886 proc layoutrows {row endrow last} {
2887 global rowidlist rowoffsets displayorder
2888 global uparrowlen downarrowlen maxwidth mingaplen
2889 global children parentlist
2891 global commitidx curview
2892 global idinlist rowchk rowrangelist
2894 set idlist [lindex $rowidlist $row]
2895 set offs [lindex $rowoffsets $row]
2896 while {$row < $endrow} {
2897 set id [lindex $displayorder $row]
2898 set nev [expr {[llength $idlist] - $maxwidth + 1}]
2899 foreach p [lindex $parentlist $row] {
2900 if {![info exists idinlist($p)] || !$idinlist($p)} {
2906 $row + $uparrowlen + $mingaplen >= $commitidx($curview)} break
2907 for {set x [llength $idlist]} {[incr x -1] >= 0} {} {
2908 set i [lindex $idlist $x]
2909 if {![info exists rowchk($i)] || $row >= $rowchk($i)} {
2910 set r [usedinrange $i [expr {$row - $downarrowlen}] \
2911 [expr {$row + $uparrowlen + $mingaplen}]]
2913 set idlist [lreplace $idlist $x $x]
2914 set offs [lreplace $offs $x $x]
2915 set offs [incrange $offs $x 1]
2917 set rm1 [expr {$row - 1}]
2918 lappend idrowranges($i) [lindex $displayorder $rm1]
2919 if {[incr nev -1] <= 0} break
2922 set rowchk($i) [expr {$row + $r}]
2925 lset rowidlist $row $idlist
2926 lset rowoffsets $row $offs
2930 foreach p [lindex $parentlist $row] {
2931 if {![info exists idinlist($p)]} {
2933 } elseif {!$idinlist($p)} {
2938 set col [lsearch -exact $idlist $id]
2940 set col [llength $idlist]
2942 lset rowidlist $row $idlist
2944 if {$children($curview,$id) ne {}} {
2945 set z [expr {[llength [lindex $rowidlist [expr {$row-1}]]] - $col}]
2949 lset rowoffsets $row $offs
2951 makeuparrow $id $col $row $z
2957 if {[info exists idrowranges($id)]} {
2958 set ranges $idrowranges($id)
2960 unset idrowranges($id)
2962 lappend rowrangelist $ranges
2964 set offs [ntimes [llength $idlist] 0]
2965 set l [llength $newolds]
2966 set idlist [eval lreplace \$idlist $col $col $newolds]
2969 set offs [lrange $offs 0 [expr {$col - 1}]]
2970 foreach x $newolds {
2975 set tmp [expr {[llength $idlist] - [llength $offs]}]
2977 set offs [concat $offs [ntimes $tmp $o]]
2982 foreach i $newolds {
2983 set idrowranges($i) $id
2986 foreach oid $oldolds {
2987 set idlist [linsert $idlist $col $oid]
2988 set offs [linsert $offs $col $o]
2989 makeuparrow $oid $col $row $o
2992 lappend rowidlist $idlist
2993 lappend rowoffsets $offs
2998 proc addextraid {id row} {
2999 global displayorder commitrow commitinfo
3000 global commitidx commitlisted
3001 global parentlist children curview
3003 incr commitidx($curview)
3004 lappend displayorder $id
3005 lappend commitlisted 0
3006 lappend parentlist {}
3007 set commitrow($curview,$id) $row
3009 if {![info exists commitinfo($id)]} {
3010 set commitinfo($id) {"No commit information available"}
3012 if {![info exists children($curview,$id)]} {
3013 set children($curview,$id) {}
3017 proc layouttail {} {
3018 global rowidlist rowoffsets idinlist commitidx curview
3019 global idrowranges rowrangelist
3021 set row $commitidx($curview)
3022 set idlist [lindex $rowidlist $row]
3023 while {$idlist ne {}} {
3024 set col [expr {[llength $idlist] - 1}]
3025 set id [lindex $idlist $col]
3027 catch {unset idinlist($id)}
3028 lappend idrowranges($id) $id
3029 lappend rowrangelist $idrowranges($id)
3030 unset idrowranges($id)
3032 set offs [ntimes $col 0]
3033 set idlist [lreplace $idlist $col $col]
3034 lappend rowidlist $idlist
3035 lappend rowoffsets $offs
3038 foreach id [array names idinlist] {
3041 lset rowidlist $row [list $id]
3042 lset rowoffsets $row 0
3043 makeuparrow $id 0 $row 0
3044 lappend idrowranges($id) $id
3045 lappend rowrangelist $idrowranges($id)
3046 unset idrowranges($id)
3048 lappend rowidlist {}
3049 lappend rowoffsets {}
3053 proc insert_pad {row col npad} {
3054 global rowidlist rowoffsets
3056 set pad [ntimes $npad {}]
3057 lset rowidlist $row [eval linsert [list [lindex $rowidlist $row]] $col $pad]
3058 set tmp [eval linsert [list [lindex $rowoffsets $row]] $col $pad]
3059 lset rowoffsets $row [incrange $tmp [expr {$col + $npad}] [expr {-$npad}]]
3062 proc optimize_rows {row col endrow} {
3063 global rowidlist rowoffsets displayorder
3065 for {} {$row < $endrow} {incr row} {
3066 set idlist [lindex $rowidlist $row]
3067 set offs [lindex $rowoffsets $row]
3069 for {} {$col < [llength $offs]} {incr col} {
3070 if {[lindex $idlist $col] eq {}} {
3074 set z [lindex $offs $col]
3075 if {$z eq {}} continue
3077 set x0 [expr {$col + $z}]
3078 set y0 [expr {$row - 1}]
3079 set z0 [lindex $rowoffsets $y0 $x0]
3081 set id [lindex $idlist $col]
3082 set ranges [rowranges $id]
3083 if {$ranges ne {} && $y0 > [lindex $ranges 0]} {
3087 # Looking at lines from this row to the previous row,
3088 # make them go straight up if they end in an arrow on
3089 # the previous row; otherwise make them go straight up
3091 if {$z < -1 || ($z < 0 && $isarrow)} {
3092 # Line currently goes left too much;
3093 # insert pads in the previous row, then optimize it
3094 set npad [expr {-1 - $z + $isarrow}]
3095 set offs [incrange $offs $col $npad]
3096 insert_pad $y0 $x0 $npad
3098 optimize_rows $y0 $x0 $row
3100 set z [lindex $offs $col]
3101 set x0 [expr {$col + $z}]
3102 set z0 [lindex $rowoffsets $y0 $x0]
3103 } elseif {$z > 1 || ($z > 0 && $isarrow)} {
3104 # Line currently goes right too much;
3105 # insert pads in this line and adjust the next's rowoffsets
3106 set npad [expr {$z - 1 + $isarrow}]
3107 set y1 [expr {$row + 1}]
3108 set offs2 [lindex $rowoffsets $y1]
3112 if {$z eq {} || $x1 + $z < $col} continue
3113 if {$x1 + $z > $col} {
3116 lset rowoffsets $y1 [incrange $offs2 $x1 $npad]
3119 set pad [ntimes $npad {}]
3120 set idlist [eval linsert \$idlist $col $pad]
3121 set tmp [eval linsert \$offs $col $pad]
3123 set offs [incrange $tmp $col [expr {-$npad}]]
3124 set z [lindex $offs $col]
3127 if {$z0 eq {} && !$isarrow} {
3128 # this line links to its first child on row $row-2
3129 set rm2 [expr {$row - 2}]
3130 set id [lindex $displayorder $rm2]
3131 set xc [lsearch -exact [lindex $rowidlist $rm2] $id]
3133 set z0 [expr {$xc - $x0}]
3136 # avoid lines jigging left then immediately right
3137 if {$z0 ne {} && $z < 0 && $z0 > 0} {
3138 insert_pad $y0 $x0 1
3139 set offs [incrange $offs $col 1]
3140 optimize_rows $y0 [expr {$x0 + 1}] $row
3145 # Find the first column that doesn't have a line going right
3146 for {set col [llength $idlist]} {[incr col -1] >= 0} {} {
3147 set o [lindex $offs $col]
3149 # check if this is the link to the first child
3150 set id [lindex $idlist $col]
3151 set ranges [rowranges $id]
3152 if {$ranges ne {} && $row == [lindex $ranges 0]} {
3153 # it is, work out offset to child
3154 set y0 [expr {$row - 1}]
3155 set id [lindex $displayorder $y0]
3156 set x0 [lsearch -exact [lindex $rowidlist $y0] $id]
3158 set o [expr {$x0 - $col}]
3162 if {$o eq {} || $o <= 0} break
3164 # Insert a pad at that column as long as it has a line and
3165 # isn't the last column, and adjust the next row' offsets
3166 if {$o ne {} && [incr col] < [llength $idlist]} {
3167 set y1 [expr {$row + 1}]
3168 set offs2 [lindex $rowoffsets $y1]
3172 if {$z eq {} || $x1 + $z < $col} continue
3173 lset rowoffsets $y1 [incrange $offs2 $x1 1]
3176 set idlist [linsert $idlist $col {}]
3177 set tmp [linsert $offs $col {}]
3179 set offs [incrange $tmp $col -1]
3182 lset rowidlist $row $idlist
3183 lset rowoffsets $row $offs
3189 global canvx0 linespc
3190 return [expr {$canvx0 + $col * $linespc}]
3194 global canvy0 linespc
3195 return [expr {$canvy0 + $row * $linespc}]
3198 proc linewidth {id} {
3199 global thickerline lthickness
3202 if {[info exists thickerline] && $id eq $thickerline} {
3203 set wid [expr {2 * $lthickness}]
3208 proc rowranges {id} {
3209 global phase idrowranges commitrow rowlaidout rowrangelist curview
3213 ([info exists commitrow($curview,$id)]
3214 && $commitrow($curview,$id) < $rowlaidout)} {
3215 set ranges [lindex $rowrangelist $commitrow($curview,$id)]
3216 } elseif {[info exists idrowranges($id)]} {
3217 set ranges $idrowranges($id)
3220 foreach rid $ranges {
3221 lappend linenos $commitrow($curview,$rid)
3223 if {$linenos ne {}} {
3224 lset linenos 0 [expr {[lindex $linenos 0] + 1}]
3229 # work around tk8.4 refusal to draw arrows on diagonal segments
3230 proc adjarrowhigh {coords} {
3233 set x0 [lindex $coords 0]
3234 set x1 [lindex $coords 2]
3236 set y0 [lindex $coords 1]
3237 set y1 [lindex $coords 3]
3238 if {$y0 - $y1 <= 2 * $linespc && $x1 == [lindex $coords 4]} {
3239 # we have a nearby vertical segment, just trim off the diag bit
3240 set coords [lrange $coords 2 end]
3242 set slope [expr {($x0 - $x1) / ($y0 - $y1)}]
3243 set xi [expr {$x0 - $slope * $linespc / 2}]
3244 set yi [expr {$y0 - $linespc / 2}]
3245 set coords [lreplace $coords 0 1 $xi $y0 $xi $yi]
3251 proc drawlineseg {id row endrow arrowlow} {
3252 global rowidlist displayorder iddrawn linesegs
3253 global canv colormap linespc curview maxlinelen
3255 set cols [list [lsearch -exact [lindex $rowidlist $row] $id]]
3256 set le [expr {$row + 1}]
3259 set c [lsearch -exact [lindex $rowidlist $le] $id]
3265 set x [lindex $displayorder $le]
3270 if {[info exists iddrawn($x)] || $le == $endrow} {
3271 set c [lsearch -exact [lindex $rowidlist [expr {$le+1}]] $id]
3287 if {[info exists linesegs($id)]} {
3288 set lines $linesegs($id)
3290 set r0 [lindex $li 0]
3292 if {$r0 == $le && [lindex $li 1] - $row <= $maxlinelen} {
3302 set li [lindex $lines [expr {$i-1}]]
3303 set r1 [lindex $li 1]
3304 if {$r1 == $row && $le - [lindex $li 0] <= $maxlinelen} {
3309 set x [lindex $cols [expr {$le - $row}]]
3310 set xp [lindex $cols [expr {$le - 1 - $row}]]
3311 set dir [expr {$xp - $x}]
3313 set ith [lindex $lines $i 2]
3314 set coords [$canv coords $ith]
3315 set ah [$canv itemcget $ith -arrow]
3316 set arrowhigh [expr {$ah eq "first" || $ah eq "both"}]
3317 set x2 [lindex $cols [expr {$le + 1 - $row}]]
3318 if {$x2 ne {} && $x - $x2 == $dir} {
3319 set coords [lrange $coords 0 end-2]
3322 set coords [list [xc $le $x] [yc $le]]
3325 set itl [lindex $lines [expr {$i-1}] 2]
3326 set al [$canv itemcget $itl -arrow]
3327 set arrowlow [expr {$al eq "last" || $al eq "both"}]
3328 } elseif {$arrowlow &&
3329 [lsearch -exact [lindex $rowidlist [expr {$row-1}]] $id] >= 0} {
3332 set arrow [lindex {none first last both} [expr {$arrowhigh + 2*$arrowlow}]]
3333 for {set y $le} {[incr y -1] > $row} {} {
3335 set xp [lindex $cols [expr {$y - 1 - $row}]]
3336 set ndir [expr {$xp - $x}]
3337 if {$dir != $ndir || $xp < 0} {
3338 lappend coords [xc $y $x] [yc $y]
3344 # join parent line to first child
3345 set ch [lindex $displayorder $row]
3346 set xc [lsearch -exact [lindex $rowidlist $row] $ch]
3348 puts "oops: drawlineseg: child $ch not on row $row"
3351 lappend coords [xc $row [expr {$x-1}]] [yc $row]
3352 } elseif {$xc > $x + 1} {
3353 lappend coords [xc $row [expr {$x+1}]] [yc $row]
3357 lappend coords [xc $row $x] [yc $row]
3359 set xn [xc $row $xp]
3361 # work around tk8.4 refusal to draw arrows on diagonal segments
3362 if {$arrowlow && $xn != [lindex $coords end-1]} {
3363 if {[llength $coords] < 4 ||
3364 [lindex $coords end-3] != [lindex $coords end-1] ||
3365 [lindex $coords end] - $yn > 2 * $linespc} {
3366 set xn [xc $row [expr {$xp - 0.5 * $dir}]]
3367 set yo [yc [expr {$row + 0.5}]]
3368 lappend coords $xn $yo $xn $yn
3371 lappend coords $xn $yn
3376 set coords [adjarrowhigh $coords]
3379 set t [$canv create line $coords -width [linewidth $id] \
3380 -fill $colormap($id) -tags lines.$id -arrow $arrow]
3383 set lines [linsert $lines $i [list $row $le $t]]
3385 $canv coords $ith $coords
3386 if {$arrow ne $ah} {
3387 $canv itemconf $ith -arrow $arrow
3389 lset lines $i 0 $row
3392 set xo [lsearch -exact [lindex $rowidlist [expr {$row - 1}]] $id]
3393 set ndir [expr {$xo - $xp}]
3394 set clow [$canv coords $itl]
3395 if {$dir == $ndir} {
3396 set clow [lrange $clow 2 end]
3398 set coords [concat $coords $clow]
3400 lset lines [expr {$i-1}] 1 $le
3402 set coords [adjarrowhigh $coords]
3405 # coalesce two pieces
3407 set b [lindex $lines [expr {$i-1}] 0]
3408 set e [lindex $lines $i 1]
3409 set lines [lreplace $lines [expr {$i-1}] $i [list $b $e $itl]]
3411 $canv coords $itl $coords
3412 if {$arrow ne $al} {
3413 $canv itemconf $itl -arrow $arrow
3417 set linesegs($id) $lines
3421 proc drawparentlinks {id row} {
3422 global rowidlist canv colormap curview parentlist
3425 set rowids [lindex $rowidlist $row]
3426 set col [lsearch -exact $rowids $id]
3427 if {$col < 0} return
3428 set olds [lindex $parentlist $row]
3429 set row2 [expr {$row + 1}]
3430 set x [xc $row $col]
3433 set ids [lindex $rowidlist $row2]
3434 # rmx = right-most X coord used
3437 set i [lsearch -exact $ids $p]
3439 puts "oops, parent $p of $id not in list"
3442 set x2 [xc $row2 $i]
3446 if {[lsearch -exact $rowids $p] < 0} {
3447 # drawlineseg will do this one for us
3451 # should handle duplicated parents here...
3452 set coords [list $x $y]
3453 if {$i < $col - 1} {
3454 lappend coords [xc $row [expr {$i + 1}]] $y
3455 } elseif {$i > $col + 1} {
3456 lappend coords [xc $row [expr {$i - 1}]] $y
3458 lappend coords $x2 $y2
3459 set t [$canv create line $coords -width [linewidth $p] \
3460 -fill $colormap($p) -tags lines.$p]
3464 if {$rmx > [lindex $idpos($id) 1]} {
3465 lset idpos($id) 1 $rmx
3470 proc drawlines {id} {
3473 $canv itemconf lines.$id -width [linewidth $id]
3476 proc drawcmittext {id row col} {
3477 global linespc canv canv2 canv3 canvy0 fgcolor curview
3478 global commitlisted commitinfo rowidlist parentlist
3479 global rowtextx idpos idtags idheads idotherrefs
3480 global linehtag linentag linedtag
3481 global mainfont canvxmax boldrows boldnamerows fgcolor nullid nullid2
3483 # listed is 0 for boundary, 1 for normal, 2 for left, 3 for right
3484 set listed [lindex $commitlisted $row]
3485 if {$id eq $nullid} {
3487 } elseif {$id eq $nullid2} {
3490 set ofill [expr {$listed != 0? "blue": "white"}]
3492 set x [xc $row $col]
3494 set orad [expr {$linespc / 3}]
3496 set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \
3497 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
3498 -fill $ofill -outline $fgcolor -width 1 -tags circle]
3499 } elseif {$listed == 2} {
3500 # triangle pointing left for left-side commits
3501 set t [$canv create polygon \
3502 [expr {$x - $orad}] $y \
3503 [expr {$x + $orad - 1}] [expr {$y - $orad}] \
3504 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
3505 -fill $ofill -outline $fgcolor -width 1 -tags circle]
3507 # triangle pointing right for right-side commits
3508 set t [$canv create polygon \
3509 [expr {$x + $orad - 1}] $y \
3510 [expr {$x - $orad}] [expr {$y - $orad}] \
3511 [expr {$x - $orad}] [expr {$y + $orad - 1}] \
3512 -fill $ofill -outline $fgcolor -width 1 -tags circle]
3515 $canv bind $t <1> {selcanvline {} %x %y}
3516 set rmx [llength [lindex $rowidlist $row]]
3517 set olds [lindex $parentlist $row]
3519 set nextids [lindex $rowidlist [expr {$row + 1}]]
3521 set i [lsearch -exact $nextids $p]
3527 set xt [xc $row $rmx]
3528 set rowtextx($row) $xt
3529 set idpos($id) [list $x $xt $y]
3530 if {[info exists idtags($id)] || [info exists idheads($id)]
3531 || [info exists idotherrefs($id)]} {
3532 set xt [drawtags $id $x $xt $y]
3534 set headline [lindex $commitinfo($id) 0]
3535 set name [lindex $commitinfo($id) 1]
3536 set date [lindex $commitinfo($id) 2]
3537 set date [formatdate $date]
3540 set isbold [ishighlighted $row]
3542 lappend boldrows $row
3545 lappend boldnamerows $row
3549 set linehtag($row) [$canv create text $xt $y -anchor w -fill $fgcolor \
3550 -text $headline -font $font -tags text]
3551 $canv bind $linehtag($row) <Button-3> "rowmenu %X %Y $id"
3552 set linentag($row) [$canv2 create text 3 $y -anchor w -fill $fgcolor \
3553 -text $name -font $nfont -tags text]
3554 set linedtag($row) [$canv3 create text 3 $y -anchor w -fill $fgcolor \
3555 -text $date -font $mainfont -tags text]
3556 set xr [expr {$xt + [font measure $mainfont $headline]}]
3557 if {$xr > $canvxmax} {
3563 proc drawcmitrow {row} {
3564 global displayorder rowidlist
3565 global iddrawn markingmatches
3566 global commitinfo parentlist numcommits
3567 global filehighlight fhighlights findstring nhighlights
3568 global hlview vhighlights
3569 global highlight_related rhighlights
3571 if {$row >= $numcommits} return
3573 set id [lindex $displayorder $row]
3574 if {[info exists hlview] && ![info exists vhighlights($row)]} {
3575 askvhighlight $row $id
3577 if {[info exists filehighlight] && ![info exists fhighlights($row)]} {
3578 askfilehighlight $row $id
3580 if {$findstring ne {} && ![info exists nhighlights($row)]} {
3581 askfindhighlight $row $id
3583 if {$highlight_related ne "None" && ![info exists rhighlights($row)]} {
3584 askrelhighlight $row $id
3586 if {![info exists iddrawn($id)]} {
3587 set col [lsearch -exact [lindex $rowidlist $row] $id]
3589 puts "oops, row $row id $id not in list"
3592 if {![info exists commitinfo($id)]} {
3596 drawcmittext $id $row $col
3599 if {$markingmatches} {
3600 markrowmatches $row $id
3604 proc drawcommits {row {endrow {}}} {
3605 global numcommits iddrawn displayorder curview
3606 global parentlist rowidlist
3611 if {$endrow eq {}} {
3614 if {$endrow >= $numcommits} {
3615 set endrow [expr {$numcommits - 1}]
3618 # make the lines join to already-drawn rows either side
3619 set r [expr {$row - 1}]
3620 if {$r < 0 || ![info exists iddrawn([lindex $displayorder $r])]} {
3623 set er [expr {$endrow + 1}]
3624 if {$er >= $numcommits ||
3625 ![info exists iddrawn([lindex $displayorder $er])]} {
3628 for {} {$r <= $er} {incr r} {
3629 set id [lindex $displayorder $r]
3630 set wasdrawn [info exists iddrawn($id)]
3632 if {$r == $er} break
3633 set nextid [lindex $displayorder [expr {$r + 1}]]
3634 if {$wasdrawn && [info exists iddrawn($nextid)]} {
3635 catch {unset prevlines}
3638 drawparentlinks $id $r
3640 if {[info exists lineends($r)]} {
3641 foreach lid $lineends($r) {
3642 unset prevlines($lid)
3645 set rowids [lindex $rowidlist $r]
3646 foreach lid $rowids {
3647 if {$lid eq {}} continue
3649 # see if this is the first child of any of its parents
3650 foreach p [lindex $parentlist $r] {
3651 if {[lsearch -exact $rowids $p] < 0} {
3652 # make this line extend up to the child
3653 set le [drawlineseg $p $r $er 0]
3654 lappend lineends($le) $p
3658 } elseif {![info exists prevlines($lid)]} {
3659 set le [drawlineseg $lid $r $er 1]
3660 lappend lineends($le) $lid
3661 set prevlines($lid) 1
3667 proc drawfrac {f0 f1} {
3670 set ymax [lindex [$canv cget -scrollregion] 3]
3671 if {$ymax eq {} || $ymax == 0} return
3672 set y0 [expr {int($f0 * $ymax)}]
3673 set row [expr {int(($y0 - 3) / $linespc) - 1}]
3674 set y1 [expr {int($f1 * $ymax)}]
3675 set endrow [expr {int(($y1 - 3) / $linespc) + 1}]
3676 drawcommits $row $endrow
3679 proc drawvisible {} {
3681 eval drawfrac [$canv yview]
3684 proc clear_display {} {
3685 global iddrawn linesegs
3686 global vhighlights fhighlights nhighlights rhighlights
3689 catch {unset iddrawn}
3690 catch {unset linesegs}
3691 catch {unset vhighlights}
3692 catch {unset fhighlights}
3693 catch {unset nhighlights}
3694 catch {unset rhighlights}
3697 proc findcrossings {id} {
3698 global rowidlist parentlist numcommits rowoffsets displayorder
3702 foreach {s e} [rowranges $id] {
3703 if {$e >= $numcommits} {
3704 set e [expr {$numcommits - 1}]
3706 if {$e <= $s} continue
3707 set x [lsearch -exact [lindex $rowidlist $e] $id]
3709 puts "findcrossings: oops, no [shortids $id] in row $e"
3712 for {set row $e} {[incr row -1] >= $s} {} {
3713 set olds [lindex $parentlist $row]
3714 set kid [lindex $displayorder $row]
3715 set kidx [lsearch -exact [lindex $rowidlist $row] $kid]
3716 if {$kidx < 0} continue
3717 set nextrow [lindex $rowidlist [expr {$row + 1}]]
3719 set px [lsearch -exact $nextrow $p]
3720 if {$px < 0} continue
3721 if {($kidx < $x && $x < $px) || ($px < $x && $x < $kidx)} {
3722 if {[lsearch -exact $ccross $p] >= 0} continue
3723 if {$x == $px + ($kidx < $px? -1: 1)} {
3725 } elseif {[lsearch -exact $cross $p] < 0} {
3730 set inc [lindex $rowoffsets $row $x]
3731 if {$inc eq {}} break
3735 return [concat $ccross {{}} $cross]
3738 proc assigncolor {id} {
3739 global colormap colors nextcolor
3740 global commitrow parentlist children children curview
3742 if {[info exists colormap($id)]} return
3743 set ncolors [llength $colors]
3744 if {[info exists children($curview,$id)]} {
3745 set kids $children($curview,$id)
3749 if {[llength $kids] == 1} {
3750 set child [lindex $kids 0]
3751 if {[info exists colormap($child)]
3752 && [llength [lindex $parentlist $commitrow($curview,$child)]] == 1} {
3753 set colormap($id) $colormap($child)
3759 foreach x [findcrossings $id] {
3761 # delimiter between corner crossings and other crossings
3762 if {[llength $badcolors] >= $ncolors - 1} break
3763 set origbad $badcolors
3765 if {[info exists colormap($x)]
3766 && [lsearch -exact $badcolors $colormap($x)] < 0} {
3767 lappend badcolors $colormap($x)
3770 if {[llength $badcolors] >= $ncolors} {
3771 set badcolors $origbad
3773 set origbad $badcolors
3774 if {[llength $badcolors] < $ncolors - 1} {
3775 foreach child $kids {
3776 if {[info exists colormap($child)]
3777 && [lsearch -exact $badcolors $colormap($child)] < 0} {
3778 lappend badcolors $colormap($child)
3780 foreach p [lindex $parentlist $commitrow($curview,$child)] {
3781 if {[info exists colormap($p)]
3782 && [lsearch -exact $badcolors $colormap($p)] < 0} {
3783 lappend badcolors $colormap($p)
3787 if {[llength $badcolors] >= $ncolors} {
3788 set badcolors $origbad
3791 for {set i 0} {$i <= $ncolors} {incr i} {
3792 set c [lindex $colors $nextcolor]
3793 if {[incr nextcolor] >= $ncolors} {
3796 if {[lsearch -exact $badcolors $c]} break
3798 set colormap($id) $c
3801 proc bindline {t id} {
3804 $canv bind $t <Enter> "lineenter %x %y $id"
3805 $canv bind $t <Motion> "linemotion %x %y $id"
3806 $canv bind $t <Leave> "lineleave $id"
3807 $canv bind $t <Button-1> "lineclick %x %y $id 1"
3810 proc drawtags {id x xt y1} {
3811 global idtags idheads idotherrefs mainhead
3812 global linespc lthickness
3813 global canv mainfont commitrow rowtextx curview fgcolor bgcolor
3818 if {[info exists idtags($id)]} {
3819 set marks $idtags($id)
3820 set ntags [llength $marks]
3822 if {[info exists idheads($id)]} {
3823 set marks [concat $marks $idheads($id)]
3824 set nheads [llength $idheads($id)]
3826 if {[info exists idotherrefs($id)]} {
3827 set marks [concat $marks $idotherrefs($id)]
3833 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
3834 set yt [expr {$y1 - 0.5 * $linespc}]
3835 set yb [expr {$yt + $linespc - 1}]
3839 foreach tag $marks {
3841 if {$i >= $ntags && $i < $ntags + $nheads && $tag eq $mainhead} {
3842 set wid [font measure [concat $mainfont bold] $tag]
3844 set wid [font measure $mainfont $tag]
3848 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
3850 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
3851 -width $lthickness -fill black -tags tag.$id]
3853 foreach tag $marks x $xvals wid $wvals {
3854 set xl [expr {$x + $delta}]
3855 set xr [expr {$x + $delta + $wid + $lthickness}]
3857 if {[incr ntags -1] >= 0} {
3859 set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \
3860 $xr $yt $xr $yb $xl $yb $x [expr {$yb - $delta}] \
3861 -width 1 -outline black -fill yellow -tags tag.$id]
3862 $canv bind $t <1> [list showtag $tag 1]
3863 set rowtextx($commitrow($curview,$id)) [expr {$xr + $linespc}]
3865 # draw a head or other ref
3866 if {[incr nheads -1] >= 0} {
3868 if {$tag eq $mainhead} {
3874 set xl [expr {$xl - $delta/2}]
3875 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
3876 -width 1 -outline black -fill $col -tags tag.$id
3877 if {[regexp {^(remotes/.*/|remotes/)} $tag match remoteprefix]} {
3878 set rwid [font measure $mainfont $remoteprefix]
3879 set xi [expr {$x + 1}]
3880 set yti [expr {$yt + 1}]
3881 set xri [expr {$x + $rwid}]
3882 $canv create polygon $xi $yti $xri $yti $xri $yb $xi $yb \
3883 -width 0 -fill "#ffddaa" -tags tag.$id
3886 set t [$canv create text $xl $y1 -anchor w -text $tag -fill $fgcolor \
3887 -font $font -tags [list tag.$id text]]
3889 $canv bind $t <1> [list showtag $tag 1]
3890 } elseif {$nheads >= 0} {
3891 $canv bind $t <Button-3> [list headmenu %X %Y $id $tag]
3897 proc xcoord {i level ln} {
3898 global canvx0 xspc1 xspc2
3900 set x [expr {$canvx0 + $i * $xspc1($ln)}]
3901 if {$i > 0 && $i == $level} {
3902 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
3903 } elseif {$i > $level} {
3904 set x [expr {$x + $xspc2 - $xspc1($ln)}]
3909 proc show_status {msg} {
3910 global canv mainfont fgcolor
3913 $canv create text 3 3 -anchor nw -text $msg -font $mainfont \
3914 -tags text -fill $fgcolor
3917 # Insert a new commit as the child of the commit on row $row.
3918 # The new commit will be displayed on row $row and the commits
3919 # on that row and below will move down one row.
3920 proc insertrow {row newcmit} {
3921 global displayorder parentlist commitlisted children
3922 global commitrow curview rowidlist rowoffsets numcommits
3923 global rowrangelist rowlaidout rowoptim numcommits
3924 global selectedline rowchk commitidx
3926 if {$row >= $numcommits} {
3927 puts "oops, inserting new row $row but only have $numcommits rows"
3930 set p [lindex $displayorder $row]
3931 set displayorder [linsert $displayorder $row $newcmit]
3932 set parentlist [linsert $parentlist $row $p]
3933 set kids $children($curview,$p)
3934 lappend kids $newcmit
3935 set children($curview,$p) $kids
3936 set children($curview,$newcmit) {}
3937 set commitlisted [linsert $commitlisted $row 1]
3938 set l [llength $displayorder]
3939 for {set r $row} {$r < $l} {incr r} {
3940 set id [lindex $displayorder $r]
3941 set commitrow($curview,$id) $r
3943 incr commitidx($curview)
3945 set idlist [lindex $rowidlist $row]
3946 set offs [lindex $rowoffsets $row]
3949 if {$x eq {} || ($x eq $p && [llength $kids] == 1)} {
3955 if {[llength $kids] == 1} {
3956 set col [lsearch -exact $idlist $p]
3957 lset idlist $col $newcmit
3959 set col [llength $idlist]
3960 lappend idlist $newcmit
3962 lset rowoffsets $row $offs
3964 set rowidlist [linsert $rowidlist $row $idlist]
3965 set rowoffsets [linsert $rowoffsets [expr {$row+1}] $newoffs]
3967 set rowrangelist [linsert $rowrangelist $row {}]
3968 if {[llength $kids] > 1} {
3969 set rp1 [expr {$row + 1}]
3970 set ranges [lindex $rowrangelist $rp1]
3971 if {$ranges eq {}} {
3972 set ranges [list $newcmit $p]
3973 } elseif {[lindex $ranges end-1] eq $p} {
3974 lset ranges end-1 $newcmit
3976 lset rowrangelist $rp1 $ranges
3979 catch {unset rowchk}
3985 if {[info exists selectedline] && $selectedline >= $row} {
3991 # Remove a commit that was inserted with insertrow on row $row.
3992 proc removerow {row} {
3993 global displayorder parentlist commitlisted children
3994 global commitrow curview rowidlist rowoffsets numcommits
3995 global rowrangelist idrowranges rowlaidout rowoptim numcommits
3996 global linesegends selectedline rowchk commitidx
3998 if {$row >= $numcommits} {
3999 puts "oops, removing row $row but only have $numcommits rows"
4002 set rp1 [expr {$row + 1}]
4003 set id [lindex $displayorder $row]
4004 set p [lindex $parentlist $row]
4005 set displayorder [lreplace $displayorder $row $row]
4006 set parentlist [lreplace $parentlist $row $row]
4007 set commitlisted [lreplace $commitlisted $row $row]
4008 set kids $children($curview,$p)
4009 set i [lsearch -exact $kids $id]
4011 set kids [lreplace $kids $i $i]
4012 set children($curview,$p) $kids
4014 set l [llength $displayorder]
4015 for {set r $row} {$r < $l} {incr r} {
4016 set id [lindex $displayorder $r]
4017 set commitrow($curview,$id) $r
4019 incr commitidx($curview) -1
4021 set rowidlist [lreplace $rowidlist $row $row]
4022 set rowoffsets [lreplace $rowoffsets $rp1 $rp1]
4024 set offs [lindex $rowoffsets $row]
4025 set offs [lreplace $offs end end]
4026 lset rowoffsets $row $offs
4029 set rowrangelist [lreplace $rowrangelist $row $row]
4030 if {[llength $kids] > 0} {
4031 set ranges [lindex $rowrangelist $row]
4032 if {[lindex $ranges end-1] eq $id} {
4033 set ranges [lreplace $ranges end-1 end]
4034 lset rowrangelist $row $ranges
4038 catch {unset rowchk}
4044 if {[info exists selectedline] && $selectedline > $row} {
4045 incr selectedline -1
4050 # Don't change the text pane cursor if it is currently the hand cursor,
4051 # showing that we are over a sha1 ID link.
4052 proc settextcursor {c} {
4053 global ctext curtextcursor
4055 if {[$ctext cget -cursor] == $curtextcursor} {
4056 $ctext config -cursor $c
4058 set curtextcursor $c
4061 proc nowbusy {what} {
4064 if {[array names isbusy] eq {}} {
4065 . config -cursor watch
4071 proc notbusy {what} {
4072 global isbusy maincursor textcursor
4074 catch {unset isbusy($what)}
4075 if {[array names isbusy] eq {}} {
4076 . config -cursor $maincursor
4077 settextcursor $textcursor
4081 proc findmatches {f} {
4082 global findtype findstring
4083 if {$findtype == "Regexp"} {
4084 set matches [regexp -indices -all -inline $findstring $f]
4087 if {$findtype == "IgnCase"} {
4088 set f [string tolower $f]
4089 set fs [string tolower $fs]
4093 set l [string length $fs]
4094 while {[set j [string first $fs $f $i]] >= 0} {
4095 lappend matches [list $j [expr {$j+$l-1}]]
4096 set i [expr {$j + $l}]
4102 proc dofind {{rev 0}} {
4103 global findstring findstartline findcurline selectedline numcommits
4106 cancel_next_highlight
4108 if {$findstring eq {} || $numcommits == 0} return
4109 if {![info exists selectedline]} {
4110 set findstartline [lindex [visiblerows] $rev]
4112 set findstartline $selectedline
4114 set findcurline $findstartline
4119 if {$findcurline == 0} {
4120 set findcurline $numcommits
4127 proc findnext {restart} {
4129 if {![info exists findcurline]} {
4143 if {![info exists findcurline]} {
4152 global commitdata commitinfo numcommits findstring findpattern findloc
4153 global findstartline findcurline displayorder
4155 set fldtypes {Headline Author Date Committer CDate Comments}
4156 set l [expr {$findcurline + 1}]
4157 if {$l >= $numcommits} {
4160 if {$l <= $findstartline} {
4161 set lim [expr {$findstartline + 1}]
4165 if {$lim - $l > 500} {
4166 set lim [expr {$l + 500}]
4169 for {} {$l < $lim} {incr l} {
4170 set id [lindex $displayorder $l]
4171 # shouldn't happen unless git log doesn't give all the commits...
4172 if {![info exists commitdata($id)]} continue
4173 if {![doesmatch $commitdata($id)]} continue
4174 if {![info exists commitinfo($id)]} {
4177 set info $commitinfo($id)
4178 foreach f $info ty $fldtypes {
4179 if {($findloc eq "All fields" || $findloc eq $ty) &&
4187 if {$l == $findstartline + 1} {
4193 set findcurline [expr {$l - 1}]
4197 proc findmorerev {} {
4198 global commitdata commitinfo numcommits findstring findpattern findloc
4199 global findstartline findcurline displayorder
4201 set fldtypes {Headline Author Date Committer CDate Comments}
4207 if {$l >= $findstartline} {
4208 set lim [expr {$findstartline - 1}]
4212 if {$l - $lim > 500} {
4213 set lim [expr {$l - 500}]
4216 for {} {$l > $lim} {incr l -1} {
4217 set id [lindex $displayorder $l]
4218 if {![doesmatch $commitdata($id)]} continue
4219 if {![info exists commitinfo($id)]} {
4222 set info $commitinfo($id)
4223 foreach f $info ty $fldtypes {
4224 if {($findloc eq "All fields" || $findloc eq $ty) &&
4238 set findcurline [expr {$l + 1}]
4242 proc findselectline {l} {
4243 global findloc commentend ctext findcurline markingmatches
4245 set markingmatches 1
4248 if {$findloc == "All fields" || $findloc == "Comments"} {
4249 # highlight the matches in the comments
4250 set f [$ctext get 1.0 $commentend]
4251 set matches [findmatches $f]
4252 foreach match $matches {
4253 set start [lindex $match 0]
4254 set end [expr {[lindex $match 1] + 1}]
4255 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
4261 # mark the bits of a headline or author that match a find string
4262 proc markmatches {canv l str tag matches font row} {
4265 set bbox [$canv bbox $tag]
4266 set x0 [lindex $bbox 0]
4267 set y0 [lindex $bbox 1]
4268 set y1 [lindex $bbox 3]
4269 foreach match $matches {
4270 set start [lindex $match 0]
4271 set end [lindex $match 1]
4272 if {$start > $end} continue
4273 set xoff [font measure $font [string range $str 0 [expr {$start-1}]]]
4274 set xlen [font measure $font [string range $str 0 [expr {$end}]]]
4275 set t [$canv create rect [expr {$x0+$xoff}] $y0 \
4276 [expr {$x0+$xlen+2}] $y1 \
4277 -outline {} -tags [list match$l matches] -fill yellow]
4279 if {[info exists selectedline] && $row == $selectedline} {
4280 $canv raise $t secsel
4285 proc unmarkmatches {} {
4286 global findids markingmatches findcurline
4288 allcanvs delete matches
4289 catch {unset findids}
4290 set markingmatches 0
4291 catch {unset findcurline}
4294 proc selcanvline {w x y} {
4295 global canv canvy0 ctext linespc
4297 set ymax [lindex [$canv cget -scrollregion] 3]
4298 if {$ymax == {}} return
4299 set yfrac [lindex [$canv yview] 0]
4300 set y [expr {$y + $yfrac * $ymax}]
4301 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
4306 if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return
4312 proc commit_descriptor {p} {
4314 if {![info exists commitinfo($p)]} {
4318 if {[llength $commitinfo($p)] > 1} {
4319 set l [lindex $commitinfo($p) 0]
4324 # append some text to the ctext widget, and make any SHA1 ID
4325 # that we know about be a clickable link.
4326 proc appendwithlinks {text tags} {
4327 global ctext commitrow linknum curview
4329 set start [$ctext index "end - 1c"]
4330 $ctext insert end $text $tags
4331 set links [regexp -indices -all -inline {[0-9a-f]{40}} $text]
4335 set linkid [string range $text $s $e]
4336 if {![info exists commitrow($curview,$linkid)]} continue
4338 $ctext tag add link "$start + $s c" "$start + $e c"
4339 $ctext tag add link$linknum "$start + $s c" "$start + $e c"
4340 $ctext tag bind link$linknum <1> \
4341 [list selectline $commitrow($curview,$linkid) 1]
4344 $ctext tag conf link -foreground blue -underline 1
4345 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
4346 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
4349 proc viewnextline {dir} {
4353 set ymax [lindex [$canv cget -scrollregion] 3]
4354 set wnow [$canv yview]
4355 set wtop [expr {[lindex $wnow 0] * $ymax}]
4356 set newtop [expr {$wtop + $dir * $linespc}]
4359 } elseif {$newtop > $ymax} {
4362 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
4365 # add a list of tag or branch names at position pos
4366 # returns the number of names inserted
4367 proc appendrefs {pos ids var} {
4368 global ctext commitrow linknum curview $var maxrefs
4370 if {[catch {$ctext index $pos}]} {
4373 $ctext conf -state normal
4374 $ctext delete $pos "$pos lineend"
4377 foreach tag [set $var\($id\)] {
4378 lappend tags [list $tag $id]
4381 if {[llength $tags] > $maxrefs} {
4382 $ctext insert $pos "many ([llength $tags])"
4384 set tags [lsort -index 0 -decreasing $tags]
4387 set id [lindex $ti 1]
4390 $ctext tag delete $lk
4391 $ctext insert $pos $sep
4392 $ctext insert $pos [lindex $ti 0] $lk
4393 if {[info exists commitrow($curview,$id)]} {
4394 $ctext tag conf $lk -foreground blue
4395 $ctext tag bind $lk <1> \
4396 [list selectline $commitrow($curview,$id) 1]
4397 $ctext tag conf $lk -underline 1
4398 $ctext tag bind $lk <Enter> { %W configure -cursor hand2 }
4399 $ctext tag bind $lk <Leave> \
4400 { %W configure -cursor $curtextcursor }
4405 $ctext conf -state disabled
4406 return [llength $tags]
4409 # called when we have finished computing the nearby tags
4410 proc dispneartags {delay} {
4411 global selectedline currentid showneartags tagphase
4413 if {![info exists selectedline] || !$showneartags} return
4414 after cancel dispnexttag
4416 after 200 dispnexttag
4419 after idle dispnexttag
4424 proc dispnexttag {} {
4425 global selectedline currentid showneartags tagphase ctext
4427 if {![info exists selectedline] || !$showneartags} return
4428 switch -- $tagphase {
4430 set dtags [desctags $currentid]
4432 appendrefs precedes $dtags idtags
4436 set atags [anctags $currentid]
4438 appendrefs follows $atags idtags
4442 set dheads [descheads $currentid]
4443 if {$dheads ne {}} {
4444 if {[appendrefs branch $dheads idheads] > 1
4445 && [$ctext get "branch -3c"] eq "h"} {
4446 # turn "Branch" into "Branches"
4447 $ctext conf -state normal
4448 $ctext insert "branch -2c" "es"
4449 $ctext conf -state disabled
4454 if {[incr tagphase] <= 2} {
4455 after idle dispnexttag
4459 proc selectline {l isnew} {
4460 global canv canv2 canv3 ctext commitinfo selectedline
4461 global displayorder linehtag linentag linedtag
4462 global canvy0 linespc parentlist children curview
4463 global currentid sha1entry
4464 global commentend idtags linknum
4465 global mergemax numcommits pending_select
4466 global cmitmode showneartags allcommits
4468 catch {unset pending_select}
4471 cancel_next_highlight
4472 if {$l < 0 || $l >= $numcommits} return
4473 set y [expr {$canvy0 + $l * $linespc}]
4474 set ymax [lindex [$canv cget -scrollregion] 3]
4475 set ytop [expr {$y - $linespc - 1}]
4476 set ybot [expr {$y + $linespc + 1}]
4477 set wnow [$canv yview]
4478 set wtop [expr {[lindex $wnow 0] * $ymax}]
4479 set wbot [expr {[lindex $wnow 1] * $ymax}]
4480 set wh [expr {$wbot - $wtop}]
4482 if {$ytop < $wtop} {
4483 if {$ybot < $wtop} {
4484 set newtop [expr {$y - $wh / 2.0}]
4487 if {$newtop > $wtop - $linespc} {
4488 set newtop [expr {$wtop - $linespc}]
4491 } elseif {$ybot > $wbot} {
4492 if {$ytop > $wbot} {
4493 set newtop [expr {$y - $wh / 2.0}]
4495 set newtop [expr {$ybot - $wh}]
4496 if {$newtop < $wtop + $linespc} {
4497 set newtop [expr {$wtop + $linespc}]
4501 if {$newtop != $wtop} {
4505 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
4509 if {![info exists linehtag($l)]} return
4511 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
4512 -tags secsel -fill [$canv cget -selectbackground]]
4514 $canv2 delete secsel
4515 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
4516 -tags secsel -fill [$canv2 cget -selectbackground]]
4518 $canv3 delete secsel
4519 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
4520 -tags secsel -fill [$canv3 cget -selectbackground]]
4524 addtohistory [list selectline $l 0]
4529 set id [lindex $displayorder $l]
4531 $sha1entry delete 0 end
4532 $sha1entry insert 0 $id
4533 $sha1entry selection from 0
4534 $sha1entry selection to end
4537 $ctext conf -state normal
4540 set info $commitinfo($id)
4541 set date [formatdate [lindex $info 2]]
4542 $ctext insert end "Author: [lindex $info 1] $date\n"
4543 set date [formatdate [lindex $info 4]]
4544 $ctext insert end "Committer: [lindex $info 3] $date\n"
4545 if {[info exists idtags($id)]} {
4546 $ctext insert end "Tags:"
4547 foreach tag $idtags($id) {
4548 $ctext insert end " $tag"
4550 $ctext insert end "\n"
4554 set olds [lindex $parentlist $l]
4555 if {[llength $olds] > 1} {
4558 if {$np >= $mergemax} {
4563 $ctext insert end "Parent: " $tag
4564 appendwithlinks [commit_descriptor $p] {}
4569 append headers "Parent: [commit_descriptor $p]"
4573 foreach c $children($curview,$id) {
4574 append headers "Child: [commit_descriptor $c]"
4577 # make anything that looks like a SHA1 ID be a clickable link
4578 appendwithlinks $headers {}
4579 if {$showneartags} {
4580 if {![info exists allcommits]} {
4583 $ctext insert end "Branch: "
4584 $ctext mark set branch "end -1c"
4585 $ctext mark gravity branch left
4586 $ctext insert end "\nFollows: "
4587 $ctext mark set follows "end -1c"
4588 $ctext mark gravity follows left
4589 $ctext insert end "\nPrecedes: "
4590 $ctext mark set precedes "end -1c"
4591 $ctext mark gravity precedes left
4592 $ctext insert end "\n"
4595 $ctext insert end "\n"
4596 set comment [lindex $info 5]
4597 if {[string first "\r" $comment] >= 0} {
4598 set comment [string map {"\r" "\n "} $comment]
4600 appendwithlinks $comment {comment}
4602 $ctext tag remove found 1.0 end
4603 $ctext conf -state disabled
4604 set commentend [$ctext index "end - 1c"]
4606 init_flist "Comments"
4607 if {$cmitmode eq "tree"} {
4609 } elseif {[llength $olds] <= 1} {
4616 proc selfirstline {} {
4621 proc sellastline {} {
4624 set l [expr {$numcommits - 1}]
4628 proc selnextline {dir} {
4631 if {![info exists selectedline]} return
4632 set l [expr {$selectedline + $dir}]
4637 proc selnextpage {dir} {
4638 global canv linespc selectedline numcommits
4640 set lpp [expr {([winfo height $canv] - 2) / $linespc}]
4644 allcanvs yview scroll [expr {$dir * $lpp}] units
4646 if {![info exists selectedline]} return
4647 set l [expr {$selectedline + $dir * $lpp}]
4650 } elseif {$l >= $numcommits} {
4651 set l [expr $numcommits - 1]
4657 proc unselectline {} {
4658 global selectedline currentid
4660 catch {unset selectedline}
4661 catch {unset currentid}
4662 allcanvs delete secsel
4664 cancel_next_highlight
4667 proc reselectline {} {
4670 if {[info exists selectedline]} {
4671 selectline $selectedline 0
4675 proc addtohistory {cmd} {
4676 global history historyindex curview
4678 set elt [list $curview $cmd]
4679 if {$historyindex > 0
4680 && [lindex $history [expr {$historyindex - 1}]] == $elt} {
4684 if {$historyindex < [llength $history]} {
4685 set history [lreplace $history $historyindex end $elt]
4687 lappend history $elt
4690 if {$historyindex > 1} {
4691 .tf.bar.leftbut conf -state normal
4693 .tf.bar.leftbut conf -state disabled
4695 .tf.bar.rightbut conf -state disabled
4701 set view [lindex $elt 0]
4702 set cmd [lindex $elt 1]
4703 if {$curview != $view} {
4710 global history historyindex
4713 if {$historyindex > 1} {
4714 incr historyindex -1
4715 godo [lindex $history [expr {$historyindex - 1}]]
4716 .tf.bar.rightbut conf -state normal
4718 if {$historyindex <= 1} {
4719 .tf.bar.leftbut conf -state disabled
4724 global history historyindex
4727 if {$historyindex < [llength $history]} {
4728 set cmd [lindex $history $historyindex]
4731 .tf.bar.leftbut conf -state normal
4733 if {$historyindex >= [llength $history]} {
4734 .tf.bar.rightbut conf -state disabled
4739 global treefilelist treeidlist diffids diffmergeid treepending
4740 global nullid nullid2
4743 catch {unset diffmergeid}
4744 if {![info exists treefilelist($id)]} {
4745 if {![info exists treepending]} {
4746 if {$id eq $nullid} {
4747 set cmd [list | git ls-files]
4748 } elseif {$id eq $nullid2} {
4749 set cmd [list | git ls-files --stage -t]
4751 set cmd [list | git ls-tree -r $id]
4753 if {[catch {set gtf [open $cmd r]}]} {
4757 set treefilelist($id) {}
4758 set treeidlist($id) {}
4759 fconfigure $gtf -blocking 0
4760 filerun $gtf [list gettreeline $gtf $id]
4767 proc gettreeline {gtf id} {
4768 global treefilelist treeidlist treepending cmitmode diffids nullid nullid2
4771 while {[incr nl] <= 1000 && [gets $gtf line] >= 0} {
4772 if {$diffids eq $nullid} {
4775 if {$diffids ne $nullid2 && [lindex $line 1] ne "blob"} continue
4776 set i [string first "\t" $line]
4777 if {$i < 0} continue
4778 set sha1 [lindex $line 2]
4779 set fname [string range $line [expr {$i+1}] end]
4780 if {[string index $fname 0] eq "\""} {
4781 set fname [lindex $fname 0]
4783 lappend treeidlist($id) $sha1
4785 lappend treefilelist($id) $fname
4788 return [expr {$nl >= 1000? 2: 1}]
4792 if {$cmitmode ne "tree"} {
4793 if {![info exists diffmergeid]} {
4794 gettreediffs $diffids
4796 } elseif {$id ne $diffids} {
4805 global treefilelist treeidlist diffids nullid nullid2
4806 global ctext commentend
4808 set i [lsearch -exact $treefilelist($diffids) $f]
4810 puts "oops, $f not in list for id $diffids"
4813 if {$diffids eq $nullid} {
4814 if {[catch {set bf [open $f r]} err]} {
4815 puts "oops, can't read $f: $err"
4819 set blob [lindex $treeidlist($diffids) $i]
4820 if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
4821 puts "oops, error reading blob $blob: $err"
4825 fconfigure $bf -blocking 0
4826 filerun $bf [list getblobline $bf $diffids]
4827 $ctext config -state normal
4828 clear_ctext $commentend
4829 $ctext insert end "\n"
4830 $ctext insert end "$f\n" filesep
4831 $ctext config -state disabled
4832 $ctext yview $commentend
4835 proc getblobline {bf id} {
4836 global diffids cmitmode ctext
4838 if {$id ne $diffids || $cmitmode ne "tree"} {
4842 $ctext config -state normal
4844 while {[incr nl] <= 1000 && [gets $bf line] >= 0} {
4845 $ctext insert end "$line\n"
4848 # delete last newline
4849 $ctext delete "end - 2c" "end - 1c"
4853 $ctext config -state disabled
4854 return [expr {$nl >= 1000? 2: 1}]
4857 proc mergediff {id l} {
4858 global diffmergeid diffopts mdifffd
4864 # this doesn't seem to actually affect anything...
4865 set env(GIT_DIFF_OPTS) $diffopts
4866 set cmd [concat | git diff-tree --no-commit-id --cc $id]
4867 if {[catch {set mdf [open $cmd r]} err]} {
4868 error_popup "Error getting merge diffs: $err"
4871 fconfigure $mdf -blocking 0
4872 set mdifffd($id) $mdf
4873 set np [llength [lindex $parentlist $l]]
4874 filerun $mdf [list getmergediffline $mdf $id $np]
4877 proc getmergediffline {mdf id np} {
4878 global diffmergeid ctext cflist mergemax
4879 global difffilestart mdifffd
4881 $ctext conf -state normal
4883 while {[incr nr] <= 1000 && [gets $mdf line] >= 0} {
4884 if {![info exists diffmergeid] || $id != $diffmergeid
4885 || $mdf != $mdifffd($id)} {
4889 if {[regexp {^diff --cc (.*)} $line match fname]} {
4890 # start of a new file
4891 $ctext insert end "\n"
4892 set here [$ctext index "end - 1c"]
4893 lappend difffilestart $here
4894 add_flist [list $fname]
4895 set l [expr {(78 - [string length $fname]) / 2}]
4896 set pad [string range "----------------------------------------" 1 $l]
4897 $ctext insert end "$pad $fname $pad\n" filesep
4898 } elseif {[regexp {^@@} $line]} {
4899 $ctext insert end "$line\n" hunksep
4900 } elseif {[regexp {^[0-9a-f]{40}$} $line] || [regexp {^index} $line]} {
4903 # parse the prefix - one ' ', '-' or '+' for each parent
4908 for {set j 0} {$j < $np} {incr j} {
4909 set c [string range $line $j $j]
4912 } elseif {$c == "-"} {
4914 } elseif {$c == "+"} {
4923 if {!$isbad && $minuses ne {} && $pluses eq {}} {
4924 # line doesn't appear in result, parents in $minuses have the line
4925 set num [lindex $minuses 0]
4926 } elseif {!$isbad && $pluses ne {} && $minuses eq {}} {
4927 # line appears in result, parents in $pluses don't have the line
4928 lappend tags mresult
4929 set num [lindex $spaces 0]
4932 if {$num >= $mergemax} {
4937 $ctext insert end "$line\n" $tags
4940 $ctext conf -state disabled
4945 return [expr {$nr >= 1000? 2: 1}]
4948 proc startdiff {ids} {
4949 global treediffs diffids treepending diffmergeid nullid nullid2
4952 catch {unset diffmergeid}
4953 if {![info exists treediffs($ids)] ||
4954 [lsearch -exact $ids $nullid] >= 0 ||
4955 [lsearch -exact $ids $nullid2] >= 0} {
4956 if {![info exists treepending]} {
4964 proc addtocflist {ids} {
4965 global treediffs cflist
4966 add_flist $treediffs($ids)
4970 proc diffcmd {ids flags} {
4971 global nullid nullid2
4973 set i [lsearch -exact $ids $nullid]
4974 set j [lsearch -exact $ids $nullid2]
4976 if {[llength $ids] > 1 && $j < 0} {
4977 # comparing working directory with some specific revision
4978 set cmd [concat | git diff-index $flags]
4980 lappend cmd -R [lindex $ids 1]
4982 lappend cmd [lindex $ids 0]
4985 # comparing working directory with index
4986 set cmd [concat | git diff-files $flags]
4991 } elseif {$j >= 0} {
4992 set cmd [concat | git diff-index --cached $flags]
4993 if {[llength $ids] > 1} {
4994 # comparing index with specific revision
4996 lappend cmd -R [lindex $ids 1]
4998 lappend cmd [lindex $ids 0]
5001 # comparing index with HEAD
5005 set cmd [concat | git diff-tree -r $flags $ids]
5010 proc gettreediffs {ids} {
5011 global treediff treepending
5013 set treepending $ids
5015 if {[catch {set gdtf [open [diffcmd $ids {--no-commit-id}] r]}]} return
5016 fconfigure $gdtf -blocking 0
5017 filerun $gdtf [list gettreediffline $gdtf $ids]
5020 proc gettreediffline {gdtf ids} {
5021 global treediff treediffs treepending diffids diffmergeid
5025 while {[incr nr] <= 1000 && [gets $gdtf line] >= 0} {
5026 set i [string first "\t" $line]
5028 set file [string range $line [expr {$i+1}] end]
5029 if {[string index $file 0] eq "\""} {
5030 set file [lindex $file 0]
5032 lappend treediff $file
5036 return [expr {$nr >= 1000? 2: 1}]
5039 set treediffs($ids) $treediff
5041 if {$cmitmode eq "tree"} {
5043 } elseif {$ids != $diffids} {
5044 if {![info exists diffmergeid]} {
5045 gettreediffs $diffids
5053 proc getblobdiffs {ids} {
5054 global diffopts blobdifffd diffids env
5055 global diffinhdr treediffs
5057 set env(GIT_DIFF_OPTS) $diffopts
5058 if {[catch {set bdf [open [diffcmd $ids {-p -C --no-commit-id}] r]} err]} {
5059 puts "error getting diffs: $err"
5063 fconfigure $bdf -blocking 0
5064 set blobdifffd($ids) $bdf
5065 filerun $bdf [list getblobdiffline $bdf $diffids]
5068 proc setinlist {var i val} {
5071 while {[llength [set $var]] < $i} {
5074 if {[llength [set $var]] == $i} {
5081 proc makediffhdr {fname ids} {
5082 global ctext curdiffstart treediffs
5084 set i [lsearch -exact $treediffs($ids) $fname]
5086 setinlist difffilestart $i $curdiffstart
5088 set l [expr {(78 - [string length $fname]) / 2}]
5089 set pad [string range "----------------------------------------" 1 $l]
5090 $ctext insert $curdiffstart "$pad $fname $pad" filesep
5093 proc getblobdiffline {bdf ids} {
5094 global diffids blobdifffd ctext curdiffstart
5095 global diffnexthead diffnextnote difffilestart
5096 global diffinhdr treediffs
5099 $ctext conf -state normal
5100 while {[incr nr] <= 1000 && [gets $bdf line] >= 0} {
5101 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
5105 if {![string compare -length 11 "diff --git " $line]} {
5106 # trim off "diff --git "
5107 set line [string range $line 11 end]
5109 # start of a new file
5110 $ctext insert end "\n"
5111 set curdiffstart [$ctext index "end - 1c"]
5112 $ctext insert end "\n" filesep
5113 # If the name hasn't changed the length will be odd,
5114 # the middle char will be a space, and the two bits either
5115 # side will be a/name and b/name, or "a/name" and "b/name".
5116 # If the name has changed we'll get "rename from" and
5117 # "rename to" lines following this, and we'll use them
5118 # to get the filenames.
5119 # This complexity is necessary because spaces in the filename(s)
5120 # don't get escaped.
5121 set l [string length $line]
5122 set i [expr {$l / 2}]
5123 if {!(($l & 1) && [string index $line $i] eq " " &&
5124 [string range $line 2 [expr {$i - 1}]] eq \
5125 [string range $line [expr {$i + 3}] end])} {
5128 # unescape if quoted and chop off the a/ from the front
5129 if {[string index $line 0] eq "\""} {
5130 set fname [string range [lindex $line 0] 2 end]
5132 set fname [string range $line 2 [expr {$i - 1}]]
5134 makediffhdr $fname $ids
5136 } elseif {[regexp {^@@ -([0-9]+)(,[0-9]+)? \+([0-9]+)(,[0-9]+)? @@(.*)} \
5137 $line match f1l f1c f2l f2c rest]} {
5138 $ctext insert end "$line\n" hunksep
5141 } elseif {$diffinhdr} {
5142 if {![string compare -length 12 "rename from " $line]} {
5143 set fname [string range $line 12 end]
5144 if {[string index $fname 0] eq "\""} {
5145 set fname [lindex $fname 0]
5147 set i [lsearch -exact $treediffs($ids) $fname]
5149 setinlist difffilestart $i $curdiffstart
5151 } elseif {![string compare -length 10 $line "rename to "]} {
5152 set fname [string range $line 10 end]
5153 if {[string index $fname 0] eq "\""} {
5154 set fname [lindex $fname 0]
5156 makediffhdr $fname $ids
5157 } elseif {[string compare -length 3 $line "---"] == 0} {
5160 } elseif {[string compare -length 3 $line "+++"] == 0} {
5164 $ctext insert end "$line\n" filesep
5167 set x [string range $line 0 0]
5168 if {$x == "-" || $x == "+"} {
5169 set tag [expr {$x == "+"}]
5170 $ctext insert end "$line\n" d$tag
5171 } elseif {$x == " "} {
5172 $ctext insert end "$line\n"
5174 # "\ No newline at end of file",
5175 # or something else we don't recognize
5176 $ctext insert end "$line\n" hunksep
5180 $ctext conf -state disabled
5185 return [expr {$nr >= 1000? 2: 1}]
5188 proc changediffdisp {} {
5189 global ctext diffelide
5191 $ctext tag conf d0 -elide [lindex $diffelide 0]
5192 $ctext tag conf d1 -elide [lindex $diffelide 1]
5196 global difffilestart ctext
5197 set prev [lindex $difffilestart 0]
5198 set here [$ctext index @0,0]
5199 foreach loc $difffilestart {
5200 if {[$ctext compare $loc >= $here]} {
5210 global difffilestart ctext
5211 set here [$ctext index @0,0]
5212 foreach loc $difffilestart {
5213 if {[$ctext compare $loc > $here]} {
5220 proc clear_ctext {{first 1.0}} {
5221 global ctext smarktop smarkbot
5223 set l [lindex [split $first .] 0]
5224 if {![info exists smarktop] || [$ctext compare $first < $smarktop.0]} {
5227 if {![info exists smarkbot] || [$ctext compare $first < $smarkbot.0]} {
5230 $ctext delete $first end
5233 proc incrsearch {name ix op} {
5234 global ctext searchstring searchdirn
5236 $ctext tag remove found 1.0 end
5237 if {[catch {$ctext index anchor}]} {
5238 # no anchor set, use start of selection, or of visible area
5239 set sel [$ctext tag ranges sel]
5241 $ctext mark set anchor [lindex $sel 0]
5242 } elseif {$searchdirn eq "-forwards"} {
5243 $ctext mark set anchor @0,0
5245 $ctext mark set anchor @0,[winfo height $ctext]
5248 if {$searchstring ne {}} {
5249 set here [$ctext search $searchdirn -- $searchstring anchor]
5258 global sstring ctext searchstring searchdirn
5261 $sstring icursor end
5262 set searchdirn -forwards
5263 if {$searchstring ne {}} {
5264 set sel [$ctext tag ranges sel]
5266 set start "[lindex $sel 0] + 1c"
5267 } elseif {[catch {set start [$ctext index anchor]}]} {
5270 set match [$ctext search -count mlen -- $searchstring $start]
5271 $ctext tag remove sel 1.0 end
5277 set mend "$match + $mlen c"
5278 $ctext tag add sel $match $mend
5279 $ctext mark unset anchor
5283 proc dosearchback {} {
5284 global sstring ctext searchstring searchdirn
5287 $sstring icursor end
5288 set searchdirn -backwards
5289 if {$searchstring ne {}} {
5290 set sel [$ctext tag ranges sel]
5292 set start [lindex $sel 0]
5293 } elseif {[catch {set start [$ctext index anchor]}]} {
5294 set start @0,[winfo height $ctext]
5296 set match [$ctext search -backwards -count ml -- $searchstring $start]
5297 $ctext tag remove sel 1.0 end
5303 set mend "$match + $ml c"
5304 $ctext tag add sel $match $mend
5305 $ctext mark unset anchor
5309 proc searchmark {first last} {
5310 global ctext searchstring
5314 set match [$ctext search -count mlen -- $searchstring $mend $last.end]
5315 if {$match eq {}} break
5316 set mend "$match + $mlen c"
5317 $ctext tag add found $match $mend
5321 proc searchmarkvisible {doall} {
5322 global ctext smarktop smarkbot
5324 set topline [lindex [split [$ctext index @0,0] .] 0]
5325 set botline [lindex [split [$ctext index @0,[winfo height $ctext]] .] 0]
5326 if {$doall || $botline < $smarktop || $topline > $smarkbot} {
5327 # no overlap with previous
5328 searchmark $topline $botline
5329 set smarktop $topline
5330 set smarkbot $botline
5332 if {$topline < $smarktop} {
5333 searchmark $topline [expr {$smarktop-1}]
5334 set smarktop $topline
5336 if {$botline > $smarkbot} {
5337 searchmark [expr {$smarkbot+1}] $botline
5338 set smarkbot $botline
5343 proc scrolltext {f0 f1} {
5346 .bleft.sb set $f0 $f1
5347 if {$searchstring ne {}} {
5353 global linespc charspc canvx0 canvy0 mainfont
5354 global xspc1 xspc2 lthickness
5356 set linespc [font metrics $mainfont -linespace]
5357 set charspc [font measure $mainfont "m"]
5358 set canvy0 [expr {int(3 + 0.5 * $linespc)}]
5359 set canvx0 [expr {int(3 + 0.5 * $linespc)}]
5360 set lthickness [expr {int($linespc / 9) + 1}]
5361 set xspc1(0) $linespc
5369 set ymax [lindex [$canv cget -scrollregion] 3]
5370 if {$ymax eq {} || $ymax == 0} return
5371 set span [$canv yview]
5374 allcanvs yview moveto [lindex $span 0]
5376 if {[info exists selectedline]} {
5377 selectline $selectedline 0
5378 allcanvs yview moveto [lindex $span 0]
5382 proc incrfont {inc} {
5383 global mainfont textfont ctext canv phase cflist
5384 global charspc tabstop
5385 global stopped entries
5387 set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
5388 set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
5390 $ctext conf -font $textfont -tabs "[expr {$tabstop * $charspc}]"
5391 $cflist conf -font $textfont
5392 $ctext tag conf filesep -font [concat $textfont bold]
5393 foreach e $entries {
5394 $e conf -font $mainfont
5396 if {$phase eq "getcommits"} {
5397 $canv itemconf textitems -font $mainfont
5403 global sha1entry sha1string
5404 if {[string length $sha1string] == 40} {
5405 $sha1entry delete 0 end
5409 proc sha1change {n1 n2 op} {
5410 global sha1string currentid sha1but
5411 if {$sha1string == {}
5412 || ([info exists currentid] && $sha1string == $currentid)} {
5417 if {[$sha1but cget -state] == $state} return
5418 if {$state == "normal"} {
5419 $sha1but conf -state normal -relief raised -text "Goto: "
5421 $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
5425 proc gotocommit {} {
5426 global sha1string currentid commitrow tagids headids
5427 global displayorder numcommits curview
5429 if {$sha1string == {}
5430 || ([info exists currentid] && $sha1string == $currentid)} return
5431 if {[info exists tagids($sha1string)]} {
5432 set id $tagids($sha1string)
5433 } elseif {[info exists headids($sha1string)]} {
5434 set id $headids($sha1string)
5436 set id [string tolower $sha1string]
5437 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
5439 foreach i $displayorder {
5440 if {[string match $id* $i]} {
5444 if {$matches ne {}} {
5445 if {[llength $matches] > 1} {
5446 error_popup "Short SHA1 id $id is ambiguous"
5449 set id [lindex $matches 0]
5453 if {[info exists commitrow($curview,$id)]} {
5454 selectline $commitrow($curview,$id) 1
5457 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
5462 error_popup "$type $sha1string is not known"
5465 proc lineenter {x y id} {
5466 global hoverx hovery hoverid hovertimer
5467 global commitinfo canv
5469 if {![info exists commitinfo($id)] && ![getcommit $id]} return
5473 if {[info exists hovertimer]} {
5474 after cancel $hovertimer
5476 set hovertimer [after 500 linehover]
5480 proc linemotion {x y id} {
5481 global hoverx hovery hoverid hovertimer
5483 if {[info exists hoverid] && $id == $hoverid} {
5486 if {[info exists hovertimer]} {
5487 after cancel $hovertimer
5489 set hovertimer [after 500 linehover]
5493 proc lineleave {id} {
5494 global hoverid hovertimer canv
5496 if {[info exists hoverid] && $id == $hoverid} {
5498 if {[info exists hovertimer]} {
5499 after cancel $hovertimer
5507 global hoverx hovery hoverid hovertimer
5508 global canv linespc lthickness
5509 global commitinfo mainfont
5511 set text [lindex $commitinfo($hoverid) 0]
5512 set ymax [lindex [$canv cget -scrollregion] 3]
5513 if {$ymax == {}} return
5514 set yfrac [lindex [$canv yview] 0]
5515 set x [expr {$hoverx + 2 * $linespc}]
5516 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
5517 set x0 [expr {$x - 2 * $lthickness}]
5518 set y0 [expr {$y - 2 * $lthickness}]
5519 set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
5520 set y1 [expr {$y + $linespc + 2 * $lthickness}]
5521 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
5522 -fill \#ffff80 -outline black -width 1 -tags hover]
5524 set t [$canv create text $x $y -anchor nw -text $text -tags hover \
5529 proc clickisonarrow {id y} {
5532 set ranges [rowranges $id]
5533 set thresh [expr {2 * $lthickness + 6}]
5534 set n [expr {[llength $ranges] - 1}]
5535 for {set i 1} {$i < $n} {incr i} {
5536 set row [lindex $ranges $i]
5537 if {abs([yc $row] - $y) < $thresh} {
5544 proc arrowjump {id n y} {
5547 # 1 <-> 2, 3 <-> 4, etc...
5548 set n [expr {(($n - 1) ^ 1) + 1}]
5549 set row [lindex [rowranges $id] $n]
5551 set ymax [lindex [$canv cget -scrollregion] 3]
5552 if {$ymax eq {} || $ymax <= 0} return
5553 set view [$canv yview]
5554 set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
5555 set yfrac [expr {$yt / $ymax - $yspan / 2}]
5559 allcanvs yview moveto $yfrac
5562 proc lineclick {x y id isnew} {
5563 global ctext commitinfo children canv thickerline curview
5565 if {![info exists commitinfo($id)] && ![getcommit $id]} return
5570 # draw this line thicker than normal
5574 set ymax [lindex [$canv cget -scrollregion] 3]
5575 if {$ymax eq {}} return
5576 set yfrac [lindex [$canv yview] 0]
5577 set y [expr {$y + $yfrac * $ymax}]
5579 set dirn [clickisonarrow $id $y]
5581 arrowjump $id $dirn $y
5586 addtohistory [list lineclick $x $y $id 0]
5588 # fill the details pane with info about this line
5589 $ctext conf -state normal
5591 $ctext tag conf link -foreground blue -underline 1
5592 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
5593 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
5594 $ctext insert end "Parent:\t"
5595 $ctext insert end $id [list link link0]
5596 $ctext tag bind link0 <1> [list selbyid $id]
5597 set info $commitinfo($id)
5598 $ctext insert end "\n\t[lindex $info 0]\n"
5599 $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
5600 set date [formatdate [lindex $info 2]]
5601 $ctext insert end "\tDate:\t$date\n"
5602 set kids $children($curview,$id)
5604 $ctext insert end "\nChildren:"
5606 foreach child $kids {
5608 if {![info exists commitinfo($child)] && ![getcommit $child]} continue
5609 set info $commitinfo($child)
5610 $ctext insert end "\n\t"
5611 $ctext insert end $child [list link link$i]
5612 $ctext tag bind link$i <1> [list selbyid $child]
5613 $ctext insert end "\n\t[lindex $info 0]"
5614 $ctext insert end "\n\tAuthor:\t[lindex $info 1]"
5615 set date [formatdate [lindex $info 2]]
5616 $ctext insert end "\n\tDate:\t$date\n"
5619 $ctext conf -state disabled
5623 proc normalline {} {
5625 if {[info exists thickerline]} {
5633 global commitrow curview
5634 if {[info exists commitrow($curview,$id)]} {
5635 selectline $commitrow($curview,$id) 1
5641 if {![info exists startmstime]} {
5642 set startmstime [clock clicks -milliseconds]
5644 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
5647 proc rowmenu {x y id} {
5648 global rowctxmenu commitrow selectedline rowmenuid curview
5649 global nullid nullid2 fakerowmenu mainhead
5652 if {![info exists selectedline]
5653 || $commitrow($curview,$id) eq $selectedline} {
5658 if {$id ne $nullid && $id ne $nullid2} {
5659 set menu $rowctxmenu
5660 $menu entryconfigure 7 -label "Reset $mainhead branch to here"
5662 set menu $fakerowmenu
5664 $menu entryconfigure "Diff this*" -state $state
5665 $menu entryconfigure "Diff selected*" -state $state
5666 $menu entryconfigure "Make patch" -state $state
5667 tk_popup $menu $x $y
5670 proc diffvssel {dirn} {
5671 global rowmenuid selectedline displayorder
5673 if {![info exists selectedline]} return
5675 set oldid [lindex $displayorder $selectedline]
5676 set newid $rowmenuid
5678 set oldid $rowmenuid
5679 set newid [lindex $displayorder $selectedline]
5681 addtohistory [list doseldiff $oldid $newid]
5682 doseldiff $oldid $newid
5685 proc doseldiff {oldid newid} {
5689 $ctext conf -state normal
5692 $ctext insert end "From "
5693 $ctext tag conf link -foreground blue -underline 1
5694 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
5695 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
5696 $ctext tag bind link0 <1> [list selbyid $oldid]
5697 $ctext insert end $oldid [list link link0]
5698 $ctext insert end "\n "
5699 $ctext insert end [lindex $commitinfo($oldid) 0]
5700 $ctext insert end "\n\nTo "
5701 $ctext tag bind link1 <1> [list selbyid $newid]
5702 $ctext insert end $newid [list link link1]
5703 $ctext insert end "\n "
5704 $ctext insert end [lindex $commitinfo($newid) 0]
5705 $ctext insert end "\n"
5706 $ctext conf -state disabled
5707 $ctext tag remove found 1.0 end
5708 startdiff [list $oldid $newid]
5712 global rowmenuid currentid commitinfo patchtop patchnum
5714 if {![info exists currentid]} return
5715 set oldid $currentid
5716 set oldhead [lindex $commitinfo($oldid) 0]
5717 set newid $rowmenuid
5718 set newhead [lindex $commitinfo($newid) 0]
5721 catch {destroy $top}
5723 label $top.title -text "Generate patch"
5724 grid $top.title - -pady 10
5725 label $top.from -text "From:"
5726 entry $top.fromsha1 -width 40 -relief flat
5727 $top.fromsha1 insert 0 $oldid
5728 $top.fromsha1 conf -state readonly
5729 grid $top.from $top.fromsha1 -sticky w
5730 entry $top.fromhead -width 60 -relief flat
5731 $top.fromhead insert 0 $oldhead
5732 $top.fromhead conf -state readonly
5733 grid x $top.fromhead -sticky w
5734 label $top.to -text "To:"
5735 entry $top.tosha1 -width 40 -relief flat
5736 $top.tosha1 insert 0 $newid
5737 $top.tosha1 conf -state readonly
5738 grid $top.to $top.tosha1 -sticky w
5739 entry $top.tohead -width 60 -relief flat
5740 $top.tohead insert 0 $newhead
5741 $top.tohead conf -state readonly
5742 grid x $top.tohead -sticky w
5743 button $top.rev -text "Reverse" -command mkpatchrev -padx 5
5744 grid $top.rev x -pady 10
5745 label $top.flab -text "Output file:"
5746 entry $top.fname -width 60
5747 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
5749 grid $top.flab $top.fname -sticky w
5751 button $top.buts.gen -text "Generate" -command mkpatchgo
5752 button $top.buts.can -text "Cancel" -command mkpatchcan
5753 grid $top.buts.gen $top.buts.can
5754 grid columnconfigure $top.buts 0 -weight 1 -uniform a
5755 grid columnconfigure $top.buts 1 -weight 1 -uniform a
5756 grid $top.buts - -pady 10 -sticky ew
5760 proc mkpatchrev {} {
5763 set oldid [$patchtop.fromsha1 get]
5764 set oldhead [$patchtop.fromhead get]
5765 set newid [$patchtop.tosha1 get]
5766 set newhead [$patchtop.tohead get]
5767 foreach e [list fromsha1 fromhead tosha1 tohead] \
5768 v [list $newid $newhead $oldid $oldhead] {
5769 $patchtop.$e conf -state normal
5770 $patchtop.$e delete 0 end
5771 $patchtop.$e insert 0 $v
5772 $patchtop.$e conf -state readonly
5777 global patchtop nullid nullid2
5779 set oldid [$patchtop.fromsha1 get]
5780 set newid [$patchtop.tosha1 get]
5781 set fname [$patchtop.fname get]
5782 set cmd [diffcmd [list $oldid $newid] -p]
5783 lappend cmd >$fname &
5784 if {[catch {eval exec $cmd} err]} {
5785 error_popup "Error creating patch: $err"
5787 catch {destroy $patchtop}
5791 proc mkpatchcan {} {
5794 catch {destroy $patchtop}
5799 global rowmenuid mktagtop commitinfo
5803 catch {destroy $top}
5805 label $top.title -text "Create tag"
5806 grid $top.title - -pady 10
5807 label $top.id -text "ID:"
5808 entry $top.sha1 -width 40 -relief flat
5809 $top.sha1 insert 0 $rowmenuid
5810 $top.sha1 conf -state readonly
5811 grid $top.id $top.sha1 -sticky w
5812 entry $top.head -width 60 -relief flat
5813 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
5814 $top.head conf -state readonly
5815 grid x $top.head -sticky w
5816 label $top.tlab -text "Tag name:"
5817 entry $top.tag -width 60
5818 grid $top.tlab $top.tag -sticky w
5820 button $top.buts.gen -text "Create" -command mktaggo
5821 button $top.buts.can -text "Cancel" -command mktagcan
5822 grid $top.buts.gen $top.buts.can
5823 grid columnconfigure $top.buts 0 -weight 1 -uniform a
5824 grid columnconfigure $top.buts 1 -weight 1 -uniform a
5825 grid $top.buts - -pady 10 -sticky ew
5830 global mktagtop env tagids idtags
5832 set id [$mktagtop.sha1 get]
5833 set tag [$mktagtop.tag get]
5835 error_popup "No tag name specified"
5838 if {[info exists tagids($tag)]} {
5839 error_popup "Tag \"$tag\" already exists"
5844 set fname [file join $dir "refs/tags" $tag]
5845 set f [open $fname w]
5849 error_popup "Error creating tag: $err"
5853 set tagids($tag) $id
5854 lappend idtags($id) $tag
5859 proc redrawtags {id} {
5860 global canv linehtag commitrow idpos selectedline curview
5861 global mainfont canvxmax iddrawn
5863 if {![info exists commitrow($curview,$id)]} return
5864 if {![info exists iddrawn($id)]} return
5865 drawcommits $commitrow($curview,$id)
5866 $canv delete tag.$id
5867 set xt [eval drawtags $id $idpos($id)]
5868 $canv coords $linehtag($commitrow($curview,$id)) $xt [lindex $idpos($id) 2]
5869 set text [$canv itemcget $linehtag($commitrow($curview,$id)) -text]
5870 set xr [expr {$xt + [font measure $mainfont $text]}]
5871 if {$xr > $canvxmax} {
5875 if {[info exists selectedline]
5876 && $selectedline == $commitrow($curview,$id)} {
5877 selectline $selectedline 0
5884 catch {destroy $mktagtop}
5893 proc writecommit {} {
5894 global rowmenuid wrcomtop commitinfo wrcomcmd
5896 set top .writecommit
5898 catch {destroy $top}
5900 label $top.title -text "Write commit to file"
5901 grid $top.title - -pady 10
5902 label $top.id -text "ID:"
5903 entry $top.sha1 -width 40 -relief flat
5904 $top.sha1 insert 0 $rowmenuid
5905 $top.sha1 conf -state readonly
5906 grid $top.id $top.sha1 -sticky w
5907 entry $top.head -width 60 -relief flat
5908 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
5909 $top.head conf -state readonly
5910 grid x $top.head -sticky w
5911 label $top.clab -text "Command:"
5912 entry $top.cmd -width 60 -textvariable wrcomcmd
5913 grid $top.clab $top.cmd -sticky w -pady 10
5914 label $top.flab -text "Output file:"
5915 entry $top.fname -width 60
5916 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
5917 grid $top.flab $top.fname -sticky w
5919 button $top.buts.gen -text "Write" -command wrcomgo
5920 button $top.buts.can -text "Cancel" -command wrcomcan
5921 grid $top.buts.gen $top.buts.can
5922 grid columnconfigure $top.buts 0 -weight 1 -uniform a
5923 grid columnconfigure $top.buts 1 -weight 1 -uniform a
5924 grid $top.buts - -pady 10 -sticky ew
5931 set id [$wrcomtop.sha1 get]
5932 set cmd "echo $id | [$wrcomtop.cmd get]"
5933 set fname [$wrcomtop.fname get]
5934 if {[catch {exec sh -c $cmd >$fname &} err]} {
5935 error_popup "Error writing commit: $err"
5937 catch {destroy $wrcomtop}
5944 catch {destroy $wrcomtop}
5949 global rowmenuid mkbrtop
5952 catch {destroy $top}
5954 label $top.title -text "Create new branch"
5955 grid $top.title - -pady 10
5956 label $top.id -text "ID:"
5957 entry $top.sha1 -width 40 -relief flat
5958 $top.sha1 insert 0 $rowmenuid
5959 $top.sha1 conf -state readonly
5960 grid $top.id $top.sha1 -sticky w
5961 label $top.nlab -text "Name:"
5962 entry $top.name -width 40
5963 grid $top.nlab $top.name -sticky w
5965 button $top.buts.go -text "Create" -command [list mkbrgo $top]
5966 button $top.buts.can -text "Cancel" -command "catch {destroy $top}"
5967 grid $top.buts.go $top.buts.can
5968 grid columnconfigure $top.buts 0 -weight 1 -uniform a
5969 grid columnconfigure $top.buts 1 -weight 1 -uniform a
5970 grid $top.buts - -pady 10 -sticky ew
5975 global headids idheads
5977 set name [$top.name get]
5978 set id [$top.sha1 get]
5980 error_popup "Please specify a name for the new branch"
5983 catch {destroy $top}
5987 exec git branch $name $id
5992 set headids($name) $id
5993 lappend idheads($id) $name
6001 proc cherrypick {} {
6002 global rowmenuid curview commitrow
6005 set oldhead [exec git rev-parse HEAD]
6006 set dheads [descheads $rowmenuid]
6007 if {$dheads ne {} && [lsearch -exact $dheads $oldhead] >= 0} {
6008 set ok [confirm_popup "Commit [string range $rowmenuid 0 7] is already\
6009 included in branch $mainhead -- really re-apply it?"]
6014 # Unfortunately git-cherry-pick writes stuff to stderr even when
6015 # no error occurs, and exec takes that as an indication of error...
6016 if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
6021 set newhead [exec git rev-parse HEAD]
6022 if {$newhead eq $oldhead} {
6024 error_popup "No changes committed"
6027 addnewchild $newhead $oldhead
6028 if {[info exists commitrow($curview,$oldhead)]} {
6029 insertrow $commitrow($curview,$oldhead) $newhead
6030 if {$mainhead ne {}} {
6031 movehead $newhead $mainhead
6032 movedhead $newhead $mainhead
6041 global mainheadid mainhead rowmenuid confirm_ok resettype
6042 global showlocalchanges
6045 set w ".confirmreset"
6048 wm title $w "Confirm reset"
6049 message $w.m -text \
6050 "Reset branch $mainhead to [string range $rowmenuid 0 7]?" \
6051 -justify center -aspect 1000
6052 pack $w.m -side top -fill x -padx 20 -pady 20
6053 frame $w.f -relief sunken -border 2
6054 message $w.f.rt -text "Reset type:" -aspect 1000
6055 grid $w.f.rt -sticky w
6057 radiobutton $w.f.soft -value soft -variable resettype -justify left \
6058 -text "Soft: Leave working tree and index untouched"
6059 grid $w.f.soft -sticky w
6060 radiobutton $w.f.mixed -value mixed -variable resettype -justify left \
6061 -text "Mixed: Leave working tree untouched, reset index"
6062 grid $w.f.mixed -sticky w
6063 radiobutton $w.f.hard -value hard -variable resettype -justify left \
6064 -text "Hard: Reset working tree and index\n(discard ALL local changes)"
6065 grid $w.f.hard -sticky w
6066 pack $w.f -side top -fill x
6067 button $w.ok -text OK -command "set confirm_ok 1; destroy $w"
6068 pack $w.ok -side left -fill x -padx 20 -pady 20
6069 button $w.cancel -text Cancel -command "destroy $w"
6070 pack $w.cancel -side right -fill x -padx 20 -pady 20
6071 bind $w <Visibility> "grab $w; focus $w"
6073 if {!$confirm_ok} return
6074 if {[catch {set fd [open \
6075 [list | sh -c "git reset --$resettype $rowmenuid 2>&1"] r]} err]} {
6079 set w ".resetprogress"
6080 filerun $fd [list readresetstat $fd $w]
6083 wm title $w "Reset progress"
6084 message $w.m -text "Reset in progress, please wait..." \
6085 -justify center -aspect 1000
6086 pack $w.m -side top -fill x -padx 20 -pady 5
6087 canvas $w.c -width 150 -height 20 -bg white
6088 $w.c create rect 0 0 0 20 -fill green -tags rect
6089 pack $w.c -side top -fill x -padx 20 -pady 5 -expand 1
6094 proc readresetstat {fd w} {
6095 global mainhead mainheadid showlocalchanges
6097 if {[gets $fd line] >= 0} {
6098 if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
6099 set x [expr {($m * 150) / $n}]
6100 $w.c coords rect 0 0 $x 20
6106 if {[catch {close $fd} err]} {
6109 set oldhead $mainheadid
6110 set newhead [exec git rev-parse HEAD]
6111 if {$newhead ne $oldhead} {
6112 movehead $newhead $mainhead
6113 movedhead $newhead $mainhead
6114 set mainheadid $newhead
6118 if {$showlocalchanges} {
6124 # context menu for a head
6125 proc headmenu {x y id head} {
6126 global headmenuid headmenuhead headctxmenu mainhead
6129 set headmenuhead $head
6131 if {$head eq $mainhead} {
6134 $headctxmenu entryconfigure 0 -state $state
6135 $headctxmenu entryconfigure 1 -state $state
6136 tk_popup $headctxmenu $x $y
6140 global headmenuid headmenuhead mainhead headids
6141 global showlocalchanges mainheadid
6143 # check the tree is clean first??
6144 set oldmainhead $mainhead
6149 exec git checkout -q $headmenuhead
6155 set mainhead $headmenuhead
6156 set mainheadid $headmenuid
6157 if {[info exists headids($oldmainhead)]} {
6158 redrawtags $headids($oldmainhead)
6160 redrawtags $headmenuid
6162 if {$showlocalchanges} {
6168 global headmenuid headmenuhead mainhead
6169 global headids idheads
6171 set head $headmenuhead
6173 # this check shouldn't be needed any more...
6174 if {$head eq $mainhead} {
6175 error_popup "Cannot delete the currently checked-out branch"
6178 set dheads [descheads $id]
6179 if {$dheads eq $headids($head)} {
6180 # the stuff on this branch isn't on any other branch
6181 if {![confirm_popup "The commits on branch $head aren't on any other\
6182 branch.\nReally delete branch $head?"]} return
6186 if {[catch {exec git branch -D $head} err]} {
6191 removehead $id $head
6192 removedhead $id $head
6198 # Stuff for finding nearby tags
6199 proc getallcommits {} {
6200 global allcommits allids nbmp nextarc seeds
6210 # Called when the graph might have changed
6211 proc regetallcommits {} {
6212 global allcommits seeds
6214 set cmd [concat | git rev-list --all --parents]
6218 set fd [open $cmd r]
6219 fconfigure $fd -blocking 0
6222 filerun $fd [list getallclines $fd]
6225 # Since most commits have 1 parent and 1 child, we group strings of
6226 # such commits into "arcs" joining branch/merge points (BMPs), which
6227 # are commits that either don't have 1 parent or don't have 1 child.
6229 # arcnos(id) - incoming arcs for BMP, arc we're on for other nodes
6230 # arcout(id) - outgoing arcs for BMP
6231 # arcids(a) - list of IDs on arc including end but not start
6232 # arcstart(a) - BMP ID at start of arc
6233 # arcend(a) - BMP ID at end of arc
6234 # growing(a) - arc a is still growing
6235 # arctags(a) - IDs out of arcids (excluding end) that have tags
6236 # archeads(a) - IDs out of arcids (excluding end) that have heads
6237 # The start of an arc is at the descendent end, so "incoming" means
6238 # coming from descendents, and "outgoing" means going towards ancestors.
6240 proc getallclines {fd} {
6241 global allids allparents allchildren idtags idheads nextarc nbmp
6242 global arcnos arcids arctags arcout arcend arcstart archeads growing
6243 global seeds allcommits
6246 while {[incr nid] <= 1000 && [gets $fd line] >= 0} {
6247 set id [lindex $line 0]
6248 if {[info exists allparents($id)]} {
6253 set olds [lrange $line 1 end]
6254 set allparents($id) $olds
6255 if {![info exists allchildren($id)]} {
6256 set allchildren($id) {}
6261 if {[llength $olds] == 1 && [llength $a] == 1} {
6262 lappend arcids($a) $id
6263 if {[info exists idtags($id)]} {
6264 lappend arctags($a) $id
6266 if {[info exists idheads($id)]} {
6267 lappend archeads($a) $id
6269 if {[info exists allparents($olds)]} {
6270 # seen parent already
6271 if {![info exists arcout($olds)]} {
6274 lappend arcids($a) $olds
6275 set arcend($a) $olds
6278 lappend allchildren($olds) $id
6279 lappend arcnos($olds) $a
6284 foreach a $arcnos($id) {
6285 lappend arcids($a) $id
6292 lappend allchildren($p) $id
6293 set a [incr nextarc]
6294 set arcstart($a) $id
6301 if {[info exists allparents($p)]} {
6302 # seen it already, may need to make a new branch
6303 if {![info exists arcout($p)]} {
6306 lappend arcids($a) $p
6310 lappend arcnos($p) $a
6315 global cached_dheads cached_dtags cached_atags
6316 catch {unset cached_dheads}
6317 catch {unset cached_dtags}
6318 catch {unset cached_atags}
6321 return [expr {$nid >= 1000? 2: 1}]
6324 if {[incr allcommits -1] == 0} {
6331 proc recalcarc {a} {
6332 global arctags archeads arcids idtags idheads
6336 foreach id [lrange $arcids($a) 0 end-1] {
6337 if {[info exists idtags($id)]} {
6340 if {[info exists idheads($id)]} {
6345 set archeads($a) $ah
6349 global arcnos arcids nextarc nbmp arctags archeads idtags idheads
6350 global arcstart arcend arcout allparents growing
6353 if {[llength $a] != 1} {
6354 puts "oops splitarc called but [llength $a] arcs already"
6358 set i [lsearch -exact $arcids($a) $p]
6360 puts "oops splitarc $p not in arc $a"
6363 set na [incr nextarc]
6364 if {[info exists arcend($a)]} {
6365 set arcend($na) $arcend($a)
6367 set l [lindex $allparents([lindex $arcids($a) end]) 0]
6368 set j [lsearch -exact $arcnos($l) $a]
6369 set arcnos($l) [lreplace $arcnos($l) $j $j $na]
6371 set tail [lrange $arcids($a) [expr {$i+1}] end]
6372 set arcids($a) [lrange $arcids($a) 0 $i]
6374 set arcstart($na) $p
6376 set arcids($na) $tail
6377 if {[info exists growing($a)]} {
6384 if {[llength $arcnos($id)] == 1} {
6387 set j [lsearch -exact $arcnos($id) $a]
6388 set arcnos($id) [lreplace $arcnos($id) $j $j $na]
6392 # reconstruct tags and heads lists
6393 if {$arctags($a) ne {} || $archeads($a) ne {}} {
6398 set archeads($na) {}
6402 # Update things for a new commit added that is a child of one
6403 # existing commit. Used when cherry-picking.
6404 proc addnewchild {id p} {
6405 global allids allparents allchildren idtags nextarc nbmp
6406 global arcnos arcids arctags arcout arcend arcstart archeads growing
6410 set allparents($id) [list $p]
6411 set allchildren($id) {}
6415 lappend allchildren($p) $id
6416 set a [incr nextarc]
6417 set arcstart($a) $id
6420 set arcids($a) [list $p]
6422 if {![info exists arcout($p)]} {
6425 lappend arcnos($p) $a
6426 set arcout($id) [list $a]
6429 # Returns 1 if a is an ancestor of b, -1 if b is an ancestor of a,
6430 # or 0 if neither is true.
6431 proc anc_or_desc {a b} {
6432 global arcout arcstart arcend arcnos cached_isanc
6434 if {$arcnos($a) eq $arcnos($b)} {
6435 # Both are on the same arc(s); either both are the same BMP,
6436 # or if one is not a BMP, the other is also not a BMP or is
6437 # the BMP at end of the arc (and it only has 1 incoming arc).
6438 # Or both can be BMPs with no incoming arcs.
6439 if {$a eq $b || $arcnos($a) eq {}} {
6442 # assert {[llength $arcnos($a)] == 1}
6443 set arc [lindex $arcnos($a) 0]
6444 set i [lsearch -exact $arcids($arc) $a]
6445 set j [lsearch -exact $arcids($arc) $b]
6446 if {$i < 0 || $i > $j} {
6453 if {![info exists arcout($a)]} {
6454 set arc [lindex $arcnos($a) 0]
6455 if {[info exists arcend($arc)]} {
6456 set aend $arcend($arc)
6460 set a $arcstart($arc)
6464 if {![info exists arcout($b)]} {
6465 set arc [lindex $arcnos($b) 0]
6466 if {[info exists arcend($arc)]} {
6467 set bend $arcend($arc)
6471 set b $arcstart($arc)
6481 if {[info exists cached_isanc($a,$bend)]} {
6482 if {$cached_isanc($a,$bend)} {
6486 if {[info exists cached_isanc($b,$aend)]} {
6487 if {$cached_isanc($b,$aend)} {
6490 if {[info exists cached_isanc($a,$bend)]} {
6495 set todo [list $a $b]
6498 for {set i 0} {$i < [llength $todo]} {incr i} {
6499 set x [lindex $todo $i]
6500 if {$anc($x) eq {}} {
6503 foreach arc $arcnos($x) {
6504 set xd $arcstart($arc)
6506 set cached_isanc($a,$bend) 1
6507 set cached_isanc($b,$aend) 0
6509 } elseif {$xd eq $aend} {
6510 set cached_isanc($b,$aend) 1
6511 set cached_isanc($a,$bend) 0
6514 if {![info exists anc($xd)]} {
6515 set anc($xd) $anc($x)
6517 } elseif {$anc($xd) ne $anc($x)} {
6522 set cached_isanc($a,$bend) 0
6523 set cached_isanc($b,$aend) 0
6527 # This identifies whether $desc has an ancestor that is
6528 # a growing tip of the graph and which is not an ancestor of $anc
6529 # and returns 0 if so and 1 if not.
6530 # If we subsequently discover a tag on such a growing tip, and that
6531 # turns out to be a descendent of $anc (which it could, since we
6532 # don't necessarily see children before parents), then $desc
6533 # isn't a good choice to display as a descendent tag of
6534 # $anc (since it is the descendent of another tag which is
6535 # a descendent of $anc). Similarly, $anc isn't a good choice to
6536 # display as a ancestor tag of $desc.
6538 proc is_certain {desc anc} {
6539 global arcnos arcout arcstart arcend growing problems
6542 if {[llength $arcnos($anc)] == 1} {
6543 # tags on the same arc are certain
6544 if {$arcnos($desc) eq $arcnos($anc)} {
6547 if {![info exists arcout($anc)]} {
6548 # if $anc is partway along an arc, use the start of the arc instead
6549 set a [lindex $arcnos($anc) 0]
6550 set anc $arcstart($a)
6553 if {[llength $arcnos($desc)] > 1 || [info exists arcout($desc)]} {
6556 set a [lindex $arcnos($desc) 0]
6562 set anclist [list $x]
6566 for {set i 0} {$i < [llength $anclist] && ($nnh > 0 || $ngrowanc > 0)} {incr i} {
6567 set x [lindex $anclist $i]
6572 foreach a $arcout($x) {
6573 if {[info exists growing($a)]} {
6574 if {![info exists growanc($x)] && $dl($x)} {
6580 if {[info exists dl($y)]} {
6584 if {![info exists done($y)]} {
6587 if {[info exists growanc($x)]} {
6591 for {set k 0} {$k < [llength $xl]} {incr k} {
6592 set z [lindex $xl $k]
6593 foreach c $arcout($z) {
6594 if {[info exists arcend($c)]} {
6596 if {[info exists dl($v)] && $dl($v)} {
6598 if {![info exists done($v)]} {
6601 if {[info exists growanc($v)]} {
6611 } elseif {$y eq $anc || !$dl($x)} {
6622 foreach x [array names growanc] {
6631 proc validate_arctags {a} {
6632 global arctags idtags
6636 foreach id $arctags($a) {
6638 if {![info exists idtags($id)]} {
6639 set na [lreplace $na $i $i]
6646 proc validate_archeads {a} {
6647 global archeads idheads
6650 set na $archeads($a)
6651 foreach id $archeads($a) {
6653 if {![info exists idheads($id)]} {
6654 set na [lreplace $na $i $i]
6658 set archeads($a) $na
6661 # Return the list of IDs that have tags that are descendents of id,
6662 # ignoring IDs that are descendents of IDs already reported.
6663 proc desctags {id} {
6664 global arcnos arcstart arcids arctags idtags allparents
6665 global growing cached_dtags
6667 if {![info exists allparents($id)]} {
6670 set t1 [clock clicks -milliseconds]
6672 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
6673 # part-way along an arc; check that arc first
6674 set a [lindex $arcnos($id) 0]
6675 if {$arctags($a) ne {}} {
6677 set i [lsearch -exact $arcids($a) $id]
6679 foreach t $arctags($a) {
6680 set j [lsearch -exact $arcids($a) $t]
6688 set id $arcstart($a)
6689 if {[info exists idtags($id)]} {
6693 if {[info exists cached_dtags($id)]} {
6694 return $cached_dtags($id)
6701 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
6702 set id [lindex $todo $i]
6704 set ta [info exists hastaggedancestor($id)]
6708 # ignore tags on starting node
6709 if {!$ta && $i > 0} {
6710 if {[info exists idtags($id)]} {
6713 } elseif {[info exists cached_dtags($id)]} {
6714 set tagloc($id) $cached_dtags($id)
6718 foreach a $arcnos($id) {
6720 if {!$ta && $arctags($a) ne {}} {
6722 if {$arctags($a) ne {}} {
6723 lappend tagloc($id) [lindex $arctags($a) end]
6726 if {$ta || $arctags($a) ne {}} {
6727 set tomark [list $d]
6728 for {set j 0} {$j < [llength $tomark]} {incr j} {
6729 set dd [lindex $tomark $j]
6730 if {![info exists hastaggedancestor($dd)]} {
6731 if {[info exists done($dd)]} {
6732 foreach b $arcnos($dd) {
6733 lappend tomark $arcstart($b)
6735 if {[info exists tagloc($dd)]} {
6738 } elseif {[info exists queued($dd)]} {
6741 set hastaggedancestor($dd) 1
6745 if {![info exists queued($d)]} {
6748 if {![info exists hastaggedancestor($d)]} {
6755 foreach id [array names tagloc] {
6756 if {![info exists hastaggedancestor($id)]} {
6757 foreach t $tagloc($id) {
6758 if {[lsearch -exact $tags $t] < 0} {
6764 set t2 [clock clicks -milliseconds]
6767 # remove tags that are descendents of other tags
6768 for {set i 0} {$i < [llength $tags]} {incr i} {
6769 set a [lindex $tags $i]
6770 for {set j 0} {$j < $i} {incr j} {
6771 set b [lindex $tags $j]
6772 set r [anc_or_desc $a $b]
6774 set tags [lreplace $tags $j $j]
6777 } elseif {$r == -1} {
6778 set tags [lreplace $tags $i $i]
6785 if {[array names growing] ne {}} {
6786 # graph isn't finished, need to check if any tag could get
6787 # eclipsed by another tag coming later. Simply ignore any
6788 # tags that could later get eclipsed.
6791 if {[is_certain $t $origid]} {
6795 if {$tags eq $ctags} {
6796 set cached_dtags($origid) $tags
6801 set cached_dtags($origid) $tags
6803 set t3 [clock clicks -milliseconds]
6804 if {0 && $t3 - $t1 >= 100} {
6805 puts "iterating descendents ($loopix/[llength $todo] nodes) took\
6806 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
6812 global arcnos arcids arcout arcend arctags idtags allparents
6813 global growing cached_atags
6815 if {![info exists allparents($id)]} {
6818 set t1 [clock clicks -milliseconds]
6820 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
6821 # part-way along an arc; check that arc first
6822 set a [lindex $arcnos($id) 0]
6823 if {$arctags($a) ne {}} {
6825 set i [lsearch -exact $arcids($a) $id]
6826 foreach t $arctags($a) {
6827 set j [lsearch -exact $arcids($a) $t]
6833 if {![info exists arcend($a)]} {
6837 if {[info exists idtags($id)]} {
6841 if {[info exists cached_atags($id)]} {
6842 return $cached_atags($id)
6850 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
6851 set id [lindex $todo $i]
6853 set td [info exists hastaggeddescendent($id)]
6857 # ignore tags on starting node
6858 if {!$td && $i > 0} {
6859 if {[info exists idtags($id)]} {
6862 } elseif {[info exists cached_atags($id)]} {
6863 set tagloc($id) $cached_atags($id)
6867 foreach a $arcout($id) {
6868 if {!$td && $arctags($a) ne {}} {
6870 if {$arctags($a) ne {}} {
6871 lappend tagloc($id) [lindex $arctags($a) 0]
6874 if {![info exists arcend($a)]} continue
6876 if {$td || $arctags($a) ne {}} {
6877 set tomark [list $d]
6878 for {set j 0} {$j < [llength $tomark]} {incr j} {
6879 set dd [lindex $tomark $j]
6880 if {![info exists hastaggeddescendent($dd)]} {
6881 if {[info exists done($dd)]} {
6882 foreach b $arcout($dd) {
6883 if {[info exists arcend($b)]} {
6884 lappend tomark $arcend($b)
6887 if {[info exists tagloc($dd)]} {
6890 } elseif {[info exists queued($dd)]} {
6893 set hastaggeddescendent($dd) 1
6897 if {![info exists queued($d)]} {
6900 if {![info exists hastaggeddescendent($d)]} {
6906 set t2 [clock clicks -milliseconds]
6909 foreach id [array names tagloc] {
6910 if {![info exists hastaggeddescendent($id)]} {
6911 foreach t $tagloc($id) {
6912 if {[lsearch -exact $tags $t] < 0} {
6919 # remove tags that are ancestors of other tags
6920 for {set i 0} {$i < [llength $tags]} {incr i} {
6921 set a [lindex $tags $i]
6922 for {set j 0} {$j < $i} {incr j} {
6923 set b [lindex $tags $j]
6924 set r [anc_or_desc $a $b]
6926 set tags [lreplace $tags $j $j]
6929 } elseif {$r == 1} {
6930 set tags [lreplace $tags $i $i]
6937 if {[array names growing] ne {}} {
6938 # graph isn't finished, need to check if any tag could get
6939 # eclipsed by another tag coming later. Simply ignore any
6940 # tags that could later get eclipsed.
6943 if {[is_certain $origid $t]} {
6947 if {$tags eq $ctags} {
6948 set cached_atags($origid) $tags
6953 set cached_atags($origid) $tags
6955 set t3 [clock clicks -milliseconds]
6956 if {0 && $t3 - $t1 >= 100} {
6957 puts "iterating ancestors ($loopix/[llength $todo] nodes) took\
6958 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
6963 # Return the list of IDs that have heads that are descendents of id,
6964 # including id itself if it has a head.
6965 proc descheads {id} {
6966 global arcnos arcstart arcids archeads idheads cached_dheads
6969 if {![info exists allparents($id)]} {
6973 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
6974 # part-way along an arc; check it first
6975 set a [lindex $arcnos($id) 0]
6976 if {$archeads($a) ne {}} {
6977 validate_archeads $a
6978 set i [lsearch -exact $arcids($a) $id]
6979 foreach t $archeads($a) {
6980 set j [lsearch -exact $arcids($a) $t]
6985 set id $arcstart($a)
6991 for {set i 0} {$i < [llength $todo]} {incr i} {
6992 set id [lindex $todo $i]
6993 if {[info exists cached_dheads($id)]} {
6994 set ret [concat $ret $cached_dheads($id)]
6996 if {[info exists idheads($id)]} {
6999 foreach a $arcnos($id) {
7000 if {$archeads($a) ne {}} {
7001 validate_archeads $a
7002 if {$archeads($a) ne {}} {
7003 set ret [concat $ret $archeads($a)]
7007 if {![info exists seen($d)]} {
7014 set ret [lsort -unique $ret]
7015 set cached_dheads($origid) $ret
7016 return [concat $ret $aret]
7019 proc addedtag {id} {
7020 global arcnos arcout cached_dtags cached_atags
7022 if {![info exists arcnos($id)]} return
7023 if {![info exists arcout($id)]} {
7024 recalcarc [lindex $arcnos($id) 0]
7026 catch {unset cached_dtags}
7027 catch {unset cached_atags}
7030 proc addedhead {hid head} {
7031 global arcnos arcout cached_dheads
7033 if {![info exists arcnos($hid)]} return
7034 if {![info exists arcout($hid)]} {
7035 recalcarc [lindex $arcnos($hid) 0]
7037 catch {unset cached_dheads}
7040 proc removedhead {hid head} {
7041 global cached_dheads
7043 catch {unset cached_dheads}
7046 proc movedhead {hid head} {
7047 global arcnos arcout cached_dheads
7049 if {![info exists arcnos($hid)]} return
7050 if {![info exists arcout($hid)]} {
7051 recalcarc [lindex $arcnos($hid) 0]
7053 catch {unset cached_dheads}
7056 proc changedrefs {} {
7057 global cached_dheads cached_dtags cached_atags
7058 global arctags archeads arcnos arcout idheads idtags
7060 foreach id [concat [array names idheads] [array names idtags]] {
7061 if {[info exists arcnos($id)] && ![info exists arcout($id)]} {
7062 set a [lindex $arcnos($id) 0]
7063 if {![info exists donearc($a)]} {
7069 catch {unset cached_dtags}
7070 catch {unset cached_atags}
7071 catch {unset cached_dheads}
7074 proc rereadrefs {} {
7075 global idtags idheads idotherrefs mainhead
7077 set refids [concat [array names idtags] \
7078 [array names idheads] [array names idotherrefs]]
7079 foreach id $refids {
7080 if {![info exists ref($id)]} {
7081 set ref($id) [listrefs $id]
7084 set oldmainhead $mainhead
7087 set refids [lsort -unique [concat $refids [array names idtags] \
7088 [array names idheads] [array names idotherrefs]]]
7089 foreach id $refids {
7090 set v [listrefs $id]
7091 if {![info exists ref($id)] || $ref($id) != $v ||
7092 ($id eq $oldmainhead && $id ne $mainhead) ||
7093 ($id eq $mainhead && $id ne $oldmainhead)} {
7099 proc listrefs {id} {
7100 global idtags idheads idotherrefs
7103 if {[info exists idtags($id)]} {
7107 if {[info exists idheads($id)]} {
7111 if {[info exists idotherrefs($id)]} {
7112 set z $idotherrefs($id)
7114 return [list $x $y $z]
7117 proc showtag {tag isnew} {
7118 global ctext tagcontents tagids linknum tagobjid
7121 addtohistory [list showtag $tag 0]
7123 $ctext conf -state normal
7126 if {![info exists tagcontents($tag)]} {
7128 set tagcontents($tag) [exec git cat-file tag $tagobjid($tag)]
7131 if {[info exists tagcontents($tag)]} {
7132 set text $tagcontents($tag)
7134 set text "Tag: $tag\nId: $tagids($tag)"
7136 appendwithlinks $text {}
7137 $ctext conf -state disabled
7149 global maxwidth maxgraphpct diffopts
7150 global oldprefs prefstop showneartags showlocalchanges
7151 global bgcolor fgcolor ctext diffcolors selectbgcolor
7152 global uifont tabstop
7156 if {[winfo exists $top]} {
7160 foreach v {maxwidth maxgraphpct diffopts showneartags showlocalchanges} {
7161 set oldprefs($v) [set $v]
7164 wm title $top "Gitk preferences"
7165 label $top.ldisp -text "Commit list display options"
7166 $top.ldisp configure -font $uifont
7167 grid $top.ldisp - -sticky w -pady 10
7168 label $top.spacer -text " "
7169 label $top.maxwidthl -text "Maximum graph width (lines)" \
7171 spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
7172 grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w
7173 label $top.maxpctl -text "Maximum graph width (% of pane)" \
7175 spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
7176 grid x $top.maxpctl $top.maxpct -sticky w
7177 frame $top.showlocal
7178 label $top.showlocal.l -text "Show local changes" -font optionfont
7179 checkbutton $top.showlocal.b -variable showlocalchanges
7180 pack $top.showlocal.b $top.showlocal.l -side left
7181 grid x $top.showlocal -sticky w
7183 label $top.ddisp -text "Diff display options"
7184 $top.ddisp configure -font $uifont
7185 grid $top.ddisp - -sticky w -pady 10
7186 label $top.diffoptl -text "Options for diff program" \
7188 entry $top.diffopt -width 20 -textvariable diffopts
7189 grid x $top.diffoptl $top.diffopt -sticky w
7191 label $top.ntag.l -text "Display nearby tags" -font optionfont
7192 checkbutton $top.ntag.b -variable showneartags
7193 pack $top.ntag.b $top.ntag.l -side left
7194 grid x $top.ntag -sticky w
7195 label $top.tabstopl -text "tabstop" -font optionfont
7196 spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
7197 grid x $top.tabstopl $top.tabstop -sticky w
7199 label $top.cdisp -text "Colors: press to choose"
7200 $top.cdisp configure -font $uifont
7201 grid $top.cdisp - -sticky w -pady 10
7202 label $top.bg -padx 40 -relief sunk -background $bgcolor
7203 button $top.bgbut -text "Background" -font optionfont \
7204 -command [list choosecolor bgcolor 0 $top.bg background setbg]
7205 grid x $top.bgbut $top.bg -sticky w
7206 label $top.fg -padx 40 -relief sunk -background $fgcolor
7207 button $top.fgbut -text "Foreground" -font optionfont \
7208 -command [list choosecolor fgcolor 0 $top.fg foreground setfg]
7209 grid x $top.fgbut $top.fg -sticky w
7210 label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
7211 button $top.diffoldbut -text "Diff: old lines" -font optionfont \
7212 -command [list choosecolor diffcolors 0 $top.diffold "diff old lines" \
7213 [list $ctext tag conf d0 -foreground]]
7214 grid x $top.diffoldbut $top.diffold -sticky w
7215 label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
7216 button $top.diffnewbut -text "Diff: new lines" -font optionfont \
7217 -command [list choosecolor diffcolors 1 $top.diffnew "diff new lines" \
7218 [list $ctext tag conf d1 -foreground]]
7219 grid x $top.diffnewbut $top.diffnew -sticky w
7220 label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
7221 button $top.hunksepbut -text "Diff: hunk header" -font optionfont \
7222 -command [list choosecolor diffcolors 2 $top.hunksep \
7223 "diff hunk header" \
7224 [list $ctext tag conf hunksep -foreground]]
7225 grid x $top.hunksepbut $top.hunksep -sticky w
7226 label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
7227 button $top.selbgbut -text "Select bg" -font optionfont \
7228 -command [list choosecolor selectbgcolor 0 $top.selbgsep background setselbg]
7229 grid x $top.selbgbut $top.selbgsep -sticky w
7232 button $top.buts.ok -text "OK" -command prefsok -default active
7233 $top.buts.ok configure -font $uifont
7234 button $top.buts.can -text "Cancel" -command prefscan -default normal
7235 $top.buts.can configure -font $uifont
7236 grid $top.buts.ok $top.buts.can
7237 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7238 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7239 grid $top.buts - - -pady 10 -sticky ew
7240 bind $top <Visibility> "focus $top.buts.ok"
7243 proc choosecolor {v vi w x cmd} {
7246 set c [tk_chooseColor -initialcolor [lindex [set $v] $vi] \
7247 -title "Gitk: choose color for $x"]
7248 if {$c eq {}} return
7249 $w conf -background $c
7255 global bglist cflist
7257 $w configure -selectbackground $c
7259 $cflist tag configure highlight \
7260 -background [$cflist cget -selectbackground]
7261 allcanvs itemconf secsel -fill $c
7268 $w conf -background $c
7276 $w conf -foreground $c
7278 allcanvs itemconf text -fill $c
7279 $canv itemconf circle -outline $c
7283 global maxwidth maxgraphpct diffopts
7284 global oldprefs prefstop showneartags showlocalchanges
7286 foreach v {maxwidth maxgraphpct diffopts showneartags showlocalchanges} {
7287 set $v $oldprefs($v)
7289 catch {destroy $prefstop}
7294 global maxwidth maxgraphpct
7295 global oldprefs prefstop showneartags showlocalchanges
7296 global charspc ctext tabstop
7298 catch {destroy $prefstop}
7300 $ctext configure -tabs "[expr {$tabstop * $charspc}]"
7301 if {$showlocalchanges != $oldprefs(showlocalchanges)} {
7302 if {$showlocalchanges} {
7308 if {$maxwidth != $oldprefs(maxwidth)
7309 || $maxgraphpct != $oldprefs(maxgraphpct)} {
7311 } elseif {$showneartags != $oldprefs(showneartags)} {
7316 proc formatdate {d} {
7318 set d [clock format $d -format "%Y-%m-%d %H:%M:%S"]
7323 # This list of encoding names and aliases is distilled from
7324 # http://www.iana.org/assignments/character-sets.
7325 # Not all of them are supported by Tcl.
7326 set encoding_aliases {
7327 { ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ASCII
7328 ISO646-US US-ASCII us IBM367 cp367 csASCII }
7329 { ISO-10646-UTF-1 csISO10646UTF1 }
7330 { ISO_646.basic:1983 ref csISO646basic1983 }
7331 { INVARIANT csINVARIANT }
7332 { ISO_646.irv:1983 iso-ir-2 irv csISO2IntlRefVersion }
7333 { BS_4730 iso-ir-4 ISO646-GB gb uk csISO4UnitedKingdom }
7334 { NATS-SEFI iso-ir-8-1 csNATSSEFI }
7335 { NATS-SEFI-ADD iso-ir-8-2 csNATSSEFIADD }
7336 { NATS-DANO iso-ir-9-1 csNATSDANO }
7337 { NATS-DANO-ADD iso-ir-9-2 csNATSDANOADD }
7338 { SEN_850200_B iso-ir-10 FI ISO646-FI ISO646-SE se csISO10Swedish }
7339 { SEN_850200_C iso-ir-11 ISO646-SE2 se2 csISO11SwedishForNames }
7340 { KS_C_5601-1987 iso-ir-149 KS_C_5601-1989 KSC_5601 korean csKSC56011987 }
7341 { ISO-2022-KR csISO2022KR }
7343 { ISO-2022-JP csISO2022JP }
7344 { ISO-2022-JP-2 csISO2022JP2 }
7345 { JIS_C6220-1969-jp JIS_C6220-1969 iso-ir-13 katakana x0201-7
7347 { JIS_C6220-1969-ro iso-ir-14 jp ISO646-JP csISO14JISC6220ro }
7348 { IT iso-ir-15 ISO646-IT csISO15Italian }
7349 { PT iso-ir-16 ISO646-PT csISO16Portuguese }
7350 { ES iso-ir-17 ISO646-ES csISO17Spanish }
7351 { greek7-old iso-ir-18 csISO18Greek7Old }
7352 { latin-greek iso-ir-19 csISO19LatinGreek }
7353 { DIN_66003 iso-ir-21 de ISO646-DE csISO21German }
7354 { NF_Z_62-010_(1973) iso-ir-25 ISO646-FR1 csISO25French }
7355 { Latin-greek-1 iso-ir-27 csISO27LatinGreek1 }
7356 { ISO_5427 iso-ir-37 csISO5427Cyrillic }
7357 { JIS_C6226-1978 iso-ir-42 csISO42JISC62261978 }
7358 { BS_viewdata iso-ir-47 csISO47BSViewdata }
7359 { INIS iso-ir-49 csISO49INIS }
7360 { INIS-8 iso-ir-50 csISO50INIS8 }
7361 { INIS-cyrillic iso-ir-51 csISO51INISCyrillic }
7362 { ISO_5427:1981 iso-ir-54 ISO5427Cyrillic1981 }
7363 { ISO_5428:1980 iso-ir-55 csISO5428Greek }
7364 { GB_1988-80 iso-ir-57 cn ISO646-CN csISO57GB1988 }
7365 { GB_2312-80 iso-ir-58 chinese csISO58GB231280 }
7366 { NS_4551-1 iso-ir-60 ISO646-NO no csISO60DanishNorwegian
7368 { NS_4551-2 ISO646-NO2 iso-ir-61 no2 csISO61Norwegian2 }
7369 { NF_Z_62-010 iso-ir-69 ISO646-FR fr csISO69French }
7370 { videotex-suppl iso-ir-70 csISO70VideotexSupp1 }
7371 { PT2 iso-ir-84 ISO646-PT2 csISO84Portuguese2 }
7372 { ES2 iso-ir-85 ISO646-ES2 csISO85Spanish2 }
7373 { MSZ_7795.3 iso-ir-86 ISO646-HU hu csISO86Hungarian }
7374 { JIS_C6226-1983 iso-ir-87 x0208 JIS_X0208-1983 csISO87JISX0208 }
7375 { greek7 iso-ir-88 csISO88Greek7 }
7376 { ASMO_449 ISO_9036 arabic7 iso-ir-89 csISO89ASMO449 }
7377 { iso-ir-90 csISO90 }
7378 { JIS_C6229-1984-a iso-ir-91 jp-ocr-a csISO91JISC62291984a }
7379 { JIS_C6229-1984-b iso-ir-92 ISO646-JP-OCR-B jp-ocr-b
7380 csISO92JISC62991984b }
7381 { JIS_C6229-1984-b-add iso-ir-93 jp-ocr-b-add csISO93JIS62291984badd }
7382 { JIS_C6229-1984-hand iso-ir-94 jp-ocr-hand csISO94JIS62291984hand }
7383 { JIS_C6229-1984-hand-add iso-ir-95 jp-ocr-hand-add
7384 csISO95JIS62291984handadd }
7385 { JIS_C6229-1984-kana iso-ir-96 csISO96JISC62291984kana }
7386 { ISO_2033-1983 iso-ir-98 e13b csISO2033 }
7387 { ANSI_X3.110-1983 iso-ir-99 CSA_T500-1983 NAPLPS csISO99NAPLPS }
7388 { ISO_8859-1:1987 iso-ir-100 ISO_8859-1 ISO-8859-1 latin1 l1 IBM819
7390 { ISO_8859-2:1987 iso-ir-101 ISO_8859-2 ISO-8859-2 latin2 l2 csISOLatin2 }
7391 { T.61-7bit iso-ir-102 csISO102T617bit }
7392 { T.61-8bit T.61 iso-ir-103 csISO103T618bit }
7393 { ISO_8859-3:1988 iso-ir-109 ISO_8859-3 ISO-8859-3 latin3 l3 csISOLatin3 }
7394 { ISO_8859-4:1988 iso-ir-110 ISO_8859-4 ISO-8859-4 latin4 l4 csISOLatin4 }
7395 { ECMA-cyrillic iso-ir-111 KOI8-E csISO111ECMACyrillic }
7396 { CSA_Z243.4-1985-1 iso-ir-121 ISO646-CA csa7-1 ca csISO121Canadian1 }
7397 { CSA_Z243.4-1985-2 iso-ir-122 ISO646-CA2 csa7-2 csISO122Canadian2 }
7398 { CSA_Z243.4-1985-gr iso-ir-123 csISO123CSAZ24341985gr }
7399 { ISO_8859-6:1987 iso-ir-127 ISO_8859-6 ISO-8859-6 ECMA-114 ASMO-708
7400 arabic csISOLatinArabic }
7401 { ISO_8859-6-E csISO88596E ISO-8859-6-E }
7402 { ISO_8859-6-I csISO88596I ISO-8859-6-I }
7403 { ISO_8859-7:1987 iso-ir-126 ISO_8859-7 ISO-8859-7 ELOT_928 ECMA-118
7404 greek greek8 csISOLatinGreek }
7405 { T.101-G2 iso-ir-128 csISO128T101G2 }
7406 { ISO_8859-8:1988 iso-ir-138 ISO_8859-8 ISO-8859-8 hebrew
7408 { ISO_8859-8-E csISO88598E ISO-8859-8-E }
7409 { ISO_8859-8-I csISO88598I ISO-8859-8-I }
7410 { CSN_369103 iso-ir-139 csISO139CSN369103 }
7411 { JUS_I.B1.002 iso-ir-141 ISO646-YU js yu csISO141JUSIB1002 }
7412 { ISO_6937-2-add iso-ir-142 csISOTextComm }
7413 { IEC_P27-1 iso-ir-143 csISO143IECP271 }
7414 { ISO_8859-5:1988 iso-ir-144 ISO_8859-5 ISO-8859-5 cyrillic
7415 csISOLatinCyrillic }
7416 { JUS_I.B1.003-serb iso-ir-146 serbian csISO146Serbian }
7417 { JUS_I.B1.003-mac macedonian iso-ir-147 csISO147Macedonian }
7418 { ISO_8859-9:1989 iso-ir-148 ISO_8859-9 ISO-8859-9 latin5 l5 csISOLatin5 }
7419 { greek-ccitt iso-ir-150 csISO150 csISO150GreekCCITT }
7420 { NC_NC00-10:81 cuba iso-ir-151 ISO646-CU csISO151Cuba }
7421 { ISO_6937-2-25 iso-ir-152 csISO6937Add }
7422 { GOST_19768-74 ST_SEV_358-88 iso-ir-153 csISO153GOST1976874 }
7423 { ISO_8859-supp iso-ir-154 latin1-2-5 csISO8859Supp }
7424 { ISO_10367-box iso-ir-155 csISO10367Box }
7425 { ISO-8859-10 iso-ir-157 l6 ISO_8859-10:1992 csISOLatin6 latin6 }
7426 { latin-lap lap iso-ir-158 csISO158Lap }
7427 { JIS_X0212-1990 x0212 iso-ir-159 csISO159JISX02121990 }
7428 { DS_2089 DS2089 ISO646-DK dk csISO646Danish }
7431 { JIS_X0201 X0201 csHalfWidthKatakana }
7432 { KSC5636 ISO646-KR csKSC5636 }
7433 { ISO-10646-UCS-2 csUnicode }
7434 { ISO-10646-UCS-4 csUCS4 }
7435 { DEC-MCS dec csDECMCS }
7436 { hp-roman8 roman8 r8 csHPRoman8 }
7437 { macintosh mac csMacintosh }
7438 { IBM037 cp037 ebcdic-cp-us ebcdic-cp-ca ebcdic-cp-wt ebcdic-cp-nl
7440 { IBM038 EBCDIC-INT cp038 csIBM038 }
7441 { IBM273 CP273 csIBM273 }
7442 { IBM274 EBCDIC-BE CP274 csIBM274 }
7443 { IBM275 EBCDIC-BR cp275 csIBM275 }
7444 { IBM277 EBCDIC-CP-DK EBCDIC-CP-NO csIBM277 }
7445 { IBM278 CP278 ebcdic-cp-fi ebcdic-cp-se csIBM278 }
7446 { IBM280 CP280 ebcdic-cp-it csIBM280 }
7447 { IBM281 EBCDIC-JP-E cp281 csIBM281 }
7448 { IBM284 CP284 ebcdic-cp-es csIBM284 }
7449 { IBM285 CP285 ebcdic-cp-gb csIBM285 }
7450 { IBM290 cp290 EBCDIC-JP-kana csIBM290 }
7451 { IBM297 cp297 ebcdic-cp-fr csIBM297 }
7452 { IBM420 cp420 ebcdic-cp-ar1 csIBM420 }
7453 { IBM423 cp423 ebcdic-cp-gr csIBM423 }
7454 { IBM424 cp424 ebcdic-cp-he csIBM424 }
7455 { IBM437 cp437 437 csPC8CodePage437 }
7456 { IBM500 CP500 ebcdic-cp-be ebcdic-cp-ch csIBM500 }
7457 { IBM775 cp775 csPC775Baltic }
7458 { IBM850 cp850 850 csPC850Multilingual }
7459 { IBM851 cp851 851 csIBM851 }
7460 { IBM852 cp852 852 csPCp852 }
7461 { IBM855 cp855 855 csIBM855 }
7462 { IBM857 cp857 857 csIBM857 }
7463 { IBM860 cp860 860 csIBM860 }
7464 { IBM861 cp861 861 cp-is csIBM861 }
7465 { IBM862 cp862 862 csPC862LatinHebrew }
7466 { IBM863 cp863 863 csIBM863 }
7467 { IBM864 cp864 csIBM864 }
7468 { IBM865 cp865 865 csIBM865 }
7469 { IBM866 cp866 866 csIBM866 }
7470 { IBM868 CP868 cp-ar csIBM868 }
7471 { IBM869 cp869 869 cp-gr csIBM869 }
7472 { IBM870 CP870 ebcdic-cp-roece ebcdic-cp-yu csIBM870 }
7473 { IBM871 CP871 ebcdic-cp-is csIBM871 }
7474 { IBM880 cp880 EBCDIC-Cyrillic csIBM880 }
7475 { IBM891 cp891 csIBM891 }
7476 { IBM903 cp903 csIBM903 }
7477 { IBM904 cp904 904 csIBBM904 }
7478 { IBM905 CP905 ebcdic-cp-tr csIBM905 }
7479 { IBM918 CP918 ebcdic-cp-ar2 csIBM918 }
7480 { IBM1026 CP1026 csIBM1026 }
7481 { EBCDIC-AT-DE csIBMEBCDICATDE }
7482 { EBCDIC-AT-DE-A csEBCDICATDEA }
7483 { EBCDIC-CA-FR csEBCDICCAFR }
7484 { EBCDIC-DK-NO csEBCDICDKNO }
7485 { EBCDIC-DK-NO-A csEBCDICDKNOA }
7486 { EBCDIC-FI-SE csEBCDICFISE }
7487 { EBCDIC-FI-SE-A csEBCDICFISEA }
7488 { EBCDIC-FR csEBCDICFR }
7489 { EBCDIC-IT csEBCDICIT }
7490 { EBCDIC-PT csEBCDICPT }
7491 { EBCDIC-ES csEBCDICES }
7492 { EBCDIC-ES-A csEBCDICESA }
7493 { EBCDIC-ES-S csEBCDICESS }
7494 { EBCDIC-UK csEBCDICUK }
7495 { EBCDIC-US csEBCDICUS }
7496 { UNKNOWN-8BIT csUnknown8BiT }
7497 { MNEMONIC csMnemonic }
7502 { IBM00858 CCSID00858 CP00858 PC-Multilingual-850+euro }
7503 { IBM00924 CCSID00924 CP00924 ebcdic-Latin9--euro }
7504 { IBM01140 CCSID01140 CP01140 ebcdic-us-37+euro }
7505 { IBM01141 CCSID01141 CP01141 ebcdic-de-273+euro }
7506 { IBM01142 CCSID01142 CP01142 ebcdic-dk-277+euro ebcdic-no-277+euro }
7507 { IBM01143 CCSID01143 CP01143 ebcdic-fi-278+euro ebcdic-se-278+euro }
7508 { IBM01144 CCSID01144 CP01144 ebcdic-it-280+euro }
7509 { IBM01145 CCSID01145 CP01145 ebcdic-es-284+euro }
7510 { IBM01146 CCSID01146 CP01146 ebcdic-gb-285+euro }
7511 { IBM01147 CCSID01147 CP01147 ebcdic-fr-297+euro }
7512 { IBM01148 CCSID01148 CP01148 ebcdic-international-500+euro }
7513 { IBM01149 CCSID01149 CP01149 ebcdic-is-871+euro }
7514 { IBM1047 IBM-1047 }
7515 { PTCP154 csPTCP154 PT154 CP154 Cyrillic-Asian }
7516 { Amiga-1251 Ami1251 Amiga1251 Ami-1251 }
7517 { UNICODE-1-1 csUnicode11 }
7520 { UNICODE-1-1-UTF-7 csUnicode11UTF7 }
7521 { ISO-8859-14 iso-ir-199 ISO_8859-14:1998 ISO_8859-14 latin8 iso-celtic
7523 { ISO-8859-15 ISO_8859-15 Latin-9 }
7524 { ISO-8859-16 iso-ir-226 ISO_8859-16:2001 ISO_8859-16 latin10 l10 }
7525 { GBK CP936 MS936 windows-936 }
7526 { JIS_Encoding csJISEncoding }
7527 { Shift_JIS MS_Kanji csShiftJIS }
7528 { Extended_UNIX_Code_Packed_Format_for_Japanese csEUCPkdFmtJapanese
7530 { Extended_UNIX_Code_Fixed_Width_for_Japanese csEUCFixWidJapanese }
7531 { ISO-10646-UCS-Basic csUnicodeASCII }
7532 { ISO-10646-Unicode-Latin1 csUnicodeLatin1 ISO-10646 }
7533 { ISO-Unicode-IBM-1261 csUnicodeIBM1261 }
7534 { ISO-Unicode-IBM-1268 csUnicodeIBM1268 }
7535 { ISO-Unicode-IBM-1276 csUnicodeIBM1276 }
7536 { ISO-Unicode-IBM-1264 csUnicodeIBM1264 }
7537 { ISO-Unicode-IBM-1265 csUnicodeIBM1265 }
7538 { ISO-8859-1-Windows-3.0-Latin-1 csWindows30Latin1 }
7539 { ISO-8859-1-Windows-3.1-Latin-1 csWindows31Latin1 }
7540 { ISO-8859-2-Windows-Latin-2 csWindows31Latin2 }
7541 { ISO-8859-9-Windows-Latin-5 csWindows31Latin5 }
7542 { Adobe-Standard-Encoding csAdobeStandardEncoding }
7543 { Ventura-US csVenturaUS }
7544 { Ventura-International csVenturaInternational }
7545 { PC8-Danish-Norwegian csPC8DanishNorwegian }
7546 { PC8-Turkish csPC8Turkish }
7547 { IBM-Symbols csIBMSymbols }
7548 { IBM-Thai csIBMThai }
7549 { HP-Legal csHPLegal }
7550 { HP-Pi-font csHPPiFont }
7551 { HP-Math8 csHPMath8 }
7552 { Adobe-Symbol-Encoding csHPPSMath }
7553 { HP-DeskTop csHPDesktop }
7554 { Ventura-Math csVenturaMath }
7555 { Microsoft-Publishing csMicrosoftPublishing }
7556 { Windows-31J csWindows31J }
7561 proc tcl_encoding {enc} {
7562 global encoding_aliases
7563 set names [encoding names]
7564 set lcnames [string tolower $names]
7565 set enc [string tolower $enc]
7566 set i [lsearch -exact $lcnames $enc]
7568 # look for "isonnn" instead of "iso-nnn" or "iso_nnn"
7569 if {[regsub {^iso[-_]} $enc iso encx]} {
7570 set i [lsearch -exact $lcnames $encx]
7574 foreach l $encoding_aliases {
7575 set ll [string tolower $l]
7576 if {[lsearch -exact $ll $enc] < 0} continue
7577 # look through the aliases for one that tcl knows about
7579 set i [lsearch -exact $lcnames $e]
7581 if {[regsub {^iso[-_]} $e iso ex]} {
7582 set i [lsearch -exact $lcnames $ex]
7591 return [lindex $names $i]
7598 set diffopts "-U 5 -p"
7599 set wrcomcmd "git diff-tree --stdin -p --pretty"
7603 set gitencoding [exec git config --get i18n.commitencoding]
7605 if {$gitencoding == ""} {
7606 set gitencoding "utf-8"
7608 set tclencoding [tcl_encoding $gitencoding]
7609 if {$tclencoding == {}} {
7610 puts stderr "Warning: encoding $gitencoding is not supported by Tcl/Tk"
7613 set mainfont {Helvetica 9}
7614 set textfont {Courier 9}
7615 set uifont {Helvetica 9 bold}
7617 set findmergefiles 0
7625 set cmitmode "patch"
7626 set wrapcomment "none"
7630 set showlocalchanges 1
7632 set colors {green red blue magenta darkgrey brown orange}
7635 set diffcolors {red "#00a000" blue}
7636 set selectbgcolor gray85
7638 catch {source ~/.gitk}
7640 font create optionfont -family sans-serif -size -12
7642 # check that we can find a .git directory somewhere...
7643 if {[catch {set gitdir [gitdir]}]} {
7644 show_error {} . "Cannot find a git repository here."
7647 if {![file isdirectory $gitdir]} {
7648 show_error {} . "Cannot find the git directory \"$gitdir\"."
7653 set cmdline_files {}
7658 "-d" { set datemode 1 }
7660 set cmdline_files [lrange $argv [expr {$i + 1}] end]
7664 lappend revtreeargs $arg
7670 if {$i >= [llength $argv] && $revtreeargs ne {}} {
7671 # no -- on command line, but some arguments (other than -d)
7673 set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
7674 set cmdline_files [split $f "\n"]
7675 set n [llength $cmdline_files]
7676 set revtreeargs [lrange $revtreeargs 0 end-$n]
7677 # Unfortunately git rev-parse doesn't produce an error when
7678 # something is both a revision and a filename. To be consistent
7679 # with git log and git rev-list, check revtreeargs for filenames.
7680 foreach arg $revtreeargs {
7681 if {[file exists $arg]} {
7682 show_error {} . "Ambiguous argument '$arg': both revision\
7688 # unfortunately we get both stdout and stderr in $err,
7689 # so look for "fatal:".
7690 set i [string first "fatal:" $err]
7692 set err [string range $err [expr {$i + 6}] end]
7694 show_error {} . "Bad arguments to gitk:\n$err"
7699 set nullid "0000000000000000000000000000000000000000"
7700 set nullid2 "0000000000000000000000000000000000000001"
7708 set highlight_paths {}
7709 set searchdirn -forwards
7713 set markingmatches 0
7720 set selectedhlview None
7729 set lookingforhead 0
7735 # wait for the window to become visible
7737 wm title . "[file tail $argv0]: [file tail [pwd]]"
7740 if {$cmdline_files ne {} || $revtreeargs ne {}} {
7741 # create a view for the files/dirs specified on the command line
7745 set viewname(1) "Command line"
7746 set viewfiles(1) $cmdline_files
7747 set viewargs(1) $revtreeargs
7750 .bar.view entryconf Edit* -state normal
7751 .bar.view entryconf Delete* -state normal
7754 if {[info exists permviews]} {
7755 foreach v $permviews {
7758 set viewname($n) [lindex $v 0]
7759 set viewfiles($n) [lindex $v 1]
7760 set viewargs($n) [lindex $v 2]