X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=gitk;h=ab383b3ad29cea5c7c45a8f757941acbc3abaf33;hb=9b8dc263e1b0d470cc67a824837d8884ae3e7136;hp=ff210494ce951772ca222cf2a57c1cc9a7a2cad3;hpb=3ea06f9f7e10463ee52b7ed8cbe3b2728c9e662e;p=git.git diff --git a/gitk b/gitk index ff210494c..ab383b3ad 100755 --- a/gitk +++ b/gitk @@ -2,7 +2,7 @@ # Tcl ignores the next line -*- tcl -*- \ exec wish "$0" -- "$@" -# Copyright (C) 2005 Paul Mackerras. All rights reserved. +# Copyright (C) 2005-2006 Paul Mackerras. All rights reserved. # This program is free software; it may be used, copied, modified # and distributed under the terms of the GNU General Public Licence, # either version 2, or (at your option) any later version. @@ -17,13 +17,12 @@ proc gitdir {} { } proc start_rev_list {view} { - global startmsecs nextupdate ncmupdate + global startmsecs nextupdate global commfd leftover tclencoding datemode global viewargs viewfiles commitidx set startmsecs [clock clicks -milliseconds] set nextupdate [expr {$startmsecs + 100}] - set ncmupdate 1 set commitidx($view) 0 set args $viewargs($view) if {$viewfiles($view) ne {}} { @@ -34,10 +33,10 @@ proc start_rev_list {view} { set order "--date-order" } if {[catch { - set fd [open [concat | git-rev-list --header $order \ + set fd [open [concat | git rev-list --header $order \ --parents --boundary --default HEAD $args] r] } err]} { - puts stderr "Error executing git-rev-list: $err" + puts stderr "Error executing git rev-list: $err" exit 1 } set commfd($view) $fd @@ -79,7 +78,7 @@ proc getcommitlines {fd view} { global parentlist childlist children curview hlview global vparentlist vchildlist vdisporder vcmitlisted - set stuff [read $fd] + set stuff [read $fd 500000] if {$stuff == {}} { if {![eof $fd]} return global viewname @@ -94,10 +93,10 @@ proc getcommitlines {fd view} { } if {[string range $err 0 4] == "usage"} { set err "Gitk: error reading commits$fv:\ - bad arguments to git-rev-list." + bad arguments to git rev-list." if {$viewname($view) eq "Command line"} { append err \ - " (Note: arguments to gitk are passed to git-rev-list\ + " (Note: arguments to gitk are passed to git rev-list\ to allow selection of commits to be displayed.)" } } else { @@ -148,7 +147,7 @@ proc getcommitlines {fd view} { if {[string length $shortcmit] > 80} { set shortcmit "[string range $shortcmit 0 80]..." } - error_popup "Can't parse git-rev-list output: {$shortcmit}" + error_popup "Can't parse git rev-list output: {$shortcmit}" exit 1 } set id [lindex $ids 0] @@ -185,7 +184,7 @@ proc getcommitlines {fd view} { } if {$gotsome} { if {$view == $curview} { - layoutmore + while {[layoutmore $nextupdate]} doupdate } elseif {[info exists hlview] && $view == $hlview} { vhighlightmore } @@ -196,20 +195,13 @@ proc getcommitlines {fd view} { } proc doupdate {} { - global commfd nextupdate numcommits ncmupdate + global commfd nextupdate numcommits foreach v [array names commfd] { fileevent $commfd($v) readable {} } update set nextupdate [expr {[clock clicks -milliseconds] + 100}] - if {$numcommits < 100} { - set ncmupdate [expr {$numcommits + 1}] - } elseif {$numcommits < 10000} { - set ncmupdate [expr {$numcommits + 10}] - } else { - set ncmupdate [expr {$numcommits + 100}] - } foreach v [array names commfd] { set fd $commfd($v) fileevent $fd readable [list getcommitlines $fd $v] @@ -217,7 +209,7 @@ proc doupdate {} { } proc readcommit {id} { - if {[catch {set contents [exec git-cat-file commit $id]}]} return + if {[catch {set contents [exec git cat-file commit $id]}]} return parsecommit $id $contents 0 } @@ -238,6 +230,7 @@ proc updatecommits {} { catch {unset selectedline} catch {unset thickerline} catch {unset viewdata($n)} + discardallcommits readrefs showview $n } @@ -278,8 +271,8 @@ proc parsecommit {id contents listed} { set headline $comment } if {!$listed} { - # git-rev-list indents the comment by 4 spaces; - # if we got this via git-cat-file, add the indentation + # git rev-list indents the comment by 4 spaces; + # if we got this via git cat-file, add the indentation set newcomment {} foreach line [split $comment "\n"] { append newcomment " " @@ -311,7 +304,7 @@ proc getcommit {id} { proc readrefs {} { global tagids idtags headids idheads tagcontents - global otherrefids idotherrefs + global otherrefids idotherrefs mainhead foreach v {tagids idtags headids idheads otherrefids idotherrefs} { catch {unset $v} @@ -339,14 +332,14 @@ proc readrefs {} { set type {} set tag {} catch { - set commit [exec git-rev-parse "$id^0"] - if {"$commit" != "$id"} { + set commit [exec git rev-parse "$id^0"] + if {$commit != $id} { set tagids($name) $commit lappend idtags($commit) $name } } catch { - set tagcontents($name) [exec git-cat-file tag "$id"] + set tagcontents($name) [exec git cat-file tag $id] } } elseif { $type == "heads" } { set headids($name) $id @@ -357,23 +350,47 @@ proc readrefs {} { } } close $refd + set mainhead {} + catch { + set thehead [exec git symbolic-ref HEAD] + if {[string match "refs/heads/*" $thehead]} { + set mainhead [string range $thehead 11 end] + } + } } -proc show_error {w msg} { +proc show_error {w top msg} { message $w.m -text $msg -justify center -aspect 400 pack $w.m -side top -fill x -padx 20 -pady 20 - button $w.ok -text OK -command "destroy $w" + button $w.ok -text OK -command "destroy $top" pack $w.ok -side bottom -fill x - bind $w "grab $w; focus $w" - bind $w "destroy $w" - tkwait window $w + bind $top "grab $top; focus $top" + bind $top "destroy $top" + tkwait window $top } proc error_popup msg { set w .error toplevel $w wm transient $w . - show_error $w $msg + show_error $w $w $msg +} + +proc confirm_popup msg { + global confirm_ok + set confirm_ok 0 + set w .confirm + toplevel $w + wm transient $w . + message $w.m -text $msg -justify center -aspect 400 + pack $w.m -side top -fill x -padx 20 -pady 20 + button $w.ok -text OK -command "set confirm_ok 1; destroy $w" + pack $w.ok -side left -fill x + button $w.cancel -text Cancel -command "destroy $w" + pack $w.cancel -side right -fill x + bind $w "grab $w; focus $w" + tkwait window $w + return $confirm_ok } proc makewindow {} { @@ -382,9 +399,11 @@ proc makewindow {} { global findtype findtypemenu findloc findstring fstring geometry global entries sha1entry sha1string sha1but global maincursor textcursor curtextcursor - global rowctxmenu mergemax - global highlight_files highlight_names + global rowctxmenu mergemax wrapcomment + global highlight_files gdttype global searchstring sstring + global bgcolor fgcolor bglist fglist diffcolors + global headctxmenu menu .bar .bar add cascade -label "File" -menu .bar.file @@ -445,18 +464,19 @@ proc makewindow {} { .ctop add .ctop.top set canv .ctop.top.clist.canv canvas $canv -height $geometry(canvh) -width $geometry(canv1) \ - -bg white -bd 0 \ + -background $bgcolor -bd 0 \ -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll" .ctop.top.clist add $canv set canv2 .ctop.top.clist.canv2 canvas $canv2 -height $geometry(canvh) -width $geometry(canv2) \ - -bg white -bd 0 -yscrollincr $linespc + -background $bgcolor -bd 0 -yscrollincr $linespc .ctop.top.clist add $canv2 set canv3 .ctop.top.clist.canv3 canvas $canv3 -height $geometry(canvh) -width $geometry(canv3) \ - -bg white -bd 0 -yscrollincr $linespc + -background $bgcolor -bd 0 -yscrollincr $linespc .ctop.top.clist add $canv3 bind .ctop.top.clist {resizeclistpanes %W %w} + lappend bglist $canv $canv2 $canv3 set sha1entry .ctop.top.bar.sha1 set entries $sha1entry @@ -498,26 +518,33 @@ proc makewindow {} { set fstring .ctop.top.bar.findstring lappend entries $fstring entry $fstring -width 30 -font $textfont -textvariable findstring + trace add variable findstring write find_change pack $fstring -side left -expand 1 -fill x set findtype Exact set findtypemenu [tk_optionMenu .ctop.top.bar.findtype \ findtype Exact IgnCase Regexp] + trace add variable findtype write find_change .ctop.top.bar.findtype configure -font $uifont .ctop.top.bar.findtype.menu configure -font $uifont set findloc "All fields" tk_optionMenu .ctop.top.bar.findloc findloc "All fields" Headline \ - Comments Author Committer Files Pickaxe + Comments Author Committer + trace add variable findloc write find_change .ctop.top.bar.findloc configure -font $uifont .ctop.top.bar.findloc.menu configure -font $uifont - pack .ctop.top.bar.findloc -side right pack .ctop.top.bar.findtype -side right - # for making sure type==Exact whenever loc==Pickaxe - trace add variable findloc write findlocchange - label .ctop.top.lbar.flabel -text "Highlight: Commits touching paths:" \ + label .ctop.top.lbar.flabel -text "Highlight: Commits " \ -font $uifont pack .ctop.top.lbar.flabel -side left -fill y + set gdttype "touching paths:" + set gm [tk_optionMenu .ctop.top.lbar.gdttype gdttype "touching paths:" \ + "adding/removing string:"] + trace add variable gdttype write hfiles_change + $gm conf -font $uifont + .ctop.top.lbar.gdttype conf -font $uifont + pack .ctop.top.lbar.gdttype -side left -fill y entry .ctop.top.lbar.fent -width 25 -font $textfont \ -textvariable highlight_files trace add variable highlight_files write hfiles_change @@ -531,14 +558,15 @@ proc makewindow {} { $viewhlmenu conf -font $uifont .ctop.top.lbar.vhl conf -font $uifont pack .ctop.top.lbar.vhl -side left -fill y - label .ctop.top.lbar.alabel -text " OR author/committer:" \ - -font $uifont - pack .ctop.top.lbar.alabel -side left -fill y - entry .ctop.top.lbar.aent -width 20 -font $textfont \ - -textvariable highlight_names - trace add variable highlight_names write hnames_change - lappend entries .ctop.top.lbar.aent - pack .ctop.top.lbar.aent -side right -fill x -expand 1 + label .ctop.top.lbar.rlabel -text " OR " -font $uifont + pack .ctop.top.lbar.rlabel -side left -fill y + global highlight_related + set m [tk_optionMenu .ctop.top.lbar.relm highlight_related None \ + "Descendent" "Not descendent" "Ancestor" "Not ancestor"] + $m conf -font $uifont + .ctop.top.lbar.relm conf -font $uifont + trace add variable highlight_related write vrel_change + pack .ctop.top.lbar.relm -side left -fill y panedwindow .ctop.cdet -orient horizontal .ctop add .ctop.cdet @@ -554,18 +582,22 @@ proc makewindow {} { trace add variable searchstring write incrsearch pack $sstring -side left -expand 1 -fill x set ctext .ctop.cdet.left.ctext - text $ctext -bg white -state disabled -font $textfont \ + text $ctext -background $bgcolor -foreground $fgcolor \ + -state disabled -font $textfont \ -width $geometry(ctextw) -height $geometry(ctexth) \ -yscrollcommand scrolltext -wrap none scrollbar .ctop.cdet.left.sb -command "$ctext yview" pack .ctop.cdet.left.sb -side right -fill y pack $ctext -side left -fill both -expand 1 .ctop.cdet add .ctop.cdet.left + lappend bglist $ctext + lappend fglist $ctext + $ctext tag conf comment -wrap $wrapcomment $ctext tag conf filesep -font [concat $textfont bold] -back "#aaaaaa" - $ctext tag conf hunksep -fore blue - $ctext tag conf d0 -fore red - $ctext tag conf d1 -fore "#00a000" + $ctext tag conf hunksep -fore [lindex $diffcolors 2] + $ctext tag conf d0 -fore [lindex $diffcolors 0] + $ctext tag conf d1 -fore [lindex $diffcolors 1] $ctext tag conf m0 -fore red $ctext tag conf m1 -fore blue $ctext tag conf m2 -fore green @@ -598,11 +630,15 @@ proc makewindow {} { pack .ctop.cdet.right.mode -side top -fill x set cflist .ctop.cdet.right.cfiles set indent [font measure $mainfont "nn"] - text $cflist -width $geometry(cflistw) -background white -font $mainfont \ + text $cflist -width $geometry(cflistw) \ + -background $bgcolor -foreground $fgcolor \ + -font $mainfont \ -tabs [list $indent [expr {2 * $indent}]] \ -yscrollcommand ".ctop.cdet.right.sb set" \ -cursor [. cget -cursor] \ -spacing1 1 -spacing3 1 + lappend bglist $cflist + lappend fglist $cflist scrollbar .ctop.cdet.right.sb -command "$cflist yview" pack .ctop.cdet.right.sb -side right -fill y pack $cflist -side left -fill both -expand 1 @@ -624,6 +660,8 @@ proc makewindow {} { bindkey sellastline bind . "selnextline -1" bind . "selnextline 1" + bind . "next_highlight -1" + bind . "next_highlight 1" bindkey "goforw" bindkey "goback" bind . "selnextpage -1" @@ -655,7 +693,7 @@ proc makewindow {} { bind . doquit bind . dofind bind . {findnext 0} - bind . findprev + bind . dosearchback bind . dosearch bind . {incrfont 1} bind . {incrfont 1} @@ -683,6 +721,16 @@ proc makewindow {} { $rowctxmenu add command -label "Make patch" -command mkpatch $rowctxmenu add command -label "Create tag" -command mktag $rowctxmenu add command -label "Write commit to file" -command writecommit + $rowctxmenu add command -label "Create new branch" -command mkbranch + $rowctxmenu add command -label "Cherry-pick this commit" \ + -command cherrypick + + set headctxmenu .headctxmenu + menu $headctxmenu -tearoff 0 + $headctxmenu add command -label "Check out this branch" \ + -command cobranch + $headctxmenu add command -label "Remove this branch" \ + -command rmbranch } # mouse-2 makes all windows scan vertically, but only the one @@ -732,9 +780,10 @@ proc click {w} { proc savestuff {w} { global canv canv2 canv3 ctext cflist mainfont textfont uifont global stuffsaved findmergefiles maxgraphpct - global maxwidth + global maxwidth showneartags global viewname viewfiles viewargs viewperm nextviewnum - global cmitmode + global cmitmode wrapcomment + global colors bgcolor fgcolor diffcolors if {$stuffsaved} return if {![winfo viewable .]} return @@ -747,6 +796,12 @@ proc savestuff {w} { puts $f [list set maxgraphpct $maxgraphpct] puts $f [list set maxwidth $maxwidth] puts $f [list set cmitmode $cmitmode] + puts $f [list set wrapcomment $wrapcomment] + puts $f [list set showneartags $showneartags] + puts $f [list set bgcolor $bgcolor] + puts $f [list set fgcolor $fgcolor] + puts $f [list set colors $colors] + puts $f [list set diffcolors $diffcolors] puts $f "set geometry(width) [winfo width .ctop]" puts $f "set geometry(height) [winfo height .ctop]" puts $f "set geometry(canv1) [expr {[winfo width $canv]-2}]" @@ -884,6 +939,8 @@ Gitk key bindings: Scroll commit list down one line Scroll commit list up one page Scroll commit list down one page + Move to previous highlighted line + Move to next highlighted line , b Scroll diff view up one page Scroll diff view up one page Scroll diff view down one page @@ -891,11 +948,12 @@ u Scroll diff view up 18 lines d Scroll diff view down 18 lines Find Move to next find hit - Move to previous find hit Move to next find hit / Move to next find hit, or redo find ? Move to previous find hit f Scroll diff view to next file + Search for next hit in diff view + Search for previous hit in diff view Increase font size Increase font size Decrease font size @@ -1375,7 +1433,7 @@ proc vieweditor {top n title} { checkbutton $top.perm -text "Remember this view" -variable newviewperm($n) grid $top.perm - -pady 5 -sticky w message $top.al -aspect 1000 -font $uifont \ - -text "Commits to include (arguments to git-rev-list):" + -text "Commits to include (arguments to git rev-list):" grid $top.al - -sticky w -pady 5 entry $top.args -width 50 -textvariable newviewargs($n) \ -background white @@ -1631,7 +1689,7 @@ proc showview {n} { show_status "Reading commits..." } if {[info exists commfd($n)]} { - layoutmore + layoutmore {} } else { finishcommits } @@ -1643,7 +1701,7 @@ proc showview {n} { # Stuff relating to the highlighting facility proc ishighlighted {row} { - global vhighlights fhighlights nhighlights + global vhighlights fhighlights nhighlights rhighlights if {[info exists nhighlights($row)] && $nhighlights($row) > 0} { return $nhighlights($row) @@ -1654,14 +1712,18 @@ proc ishighlighted {row} { if {[info exists fhighlights($row)] && $fhighlights($row) > 0} { return $fhighlights($row) } + if {[info exists rhighlights($row)] && $rhighlights($row) > 0} { + return $rhighlights($row) + } return 0 } proc bolden {row font} { - global canv linehtag selectedline + global canv linehtag selectedline boldrows + lappend boldrows $row $canv itemconf $linehtag($row) -font $font - if {$row == $selectedline} { + if {[info exists selectedline] && $row == $selectedline} { $canv delete secsel set t [eval $canv create rect [$canv bbox $linehtag($row)] \ -outline {{}} -tags secsel \ @@ -1671,10 +1733,11 @@ proc bolden {row font} { } proc bolden_name {row font} { - global canv2 linentag selectedline + global canv2 linentag selectedline boldnamerows + lappend boldnamerows $row $canv2 itemconf $linentag($row) -font $font - if {$row == $selectedline} { + if {[info exists selectedline] && $row == $selectedline} { $canv2 delete secsel set t [eval $canv2 create rect [$canv2 bbox $linentag($row)] \ -outline {{}} -tags secsel \ @@ -1683,14 +1746,18 @@ proc bolden_name {row font} { } } -proc unbolden {rows} { - global mainfont +proc unbolden {} { + global mainfont boldrows - foreach row $rows { + set stillbold {} + foreach row $boldrows { if {![ishighlighted $row]} { bolden $row $mainfont + } else { + lappend stillbold $row } } + set boldrows $stillbold } proc addvhighlight {n} { @@ -1716,15 +1783,11 @@ proc addvhighlight {n} { proc delvhighlight {} { global hlview vhighlights - global selectedline if {![info exists hlview]} return unset hlview - set rows [array names vhighlights] - if {$rows ne {}} { - unset vhighlights - unbolden $rows - } + catch {unset vhighlights} + unbolden } proc vhighlightmore {} { @@ -1777,11 +1840,8 @@ proc hfiles_change {name ix op} { # delete previous highlights catch {close $filehighlight} unset filehighlight - set rows [array names fhighlights] - if {$rows ne {}} { - unset fhighlights - unbolden $rows - } + catch {unset fhighlights} + unbolden unhighlight_filelist } set highlight_paths {} @@ -1807,96 +1867,121 @@ proc makepatterns {l} { } proc do_file_hl {serial} { - global highlight_files filehighlight highlight_paths + global highlight_files filehighlight highlight_paths gdttype fhl_list - if {[catch {set paths [shellsplit $highlight_files]}]} return - set highlight_paths [makepatterns $paths] - highlight_filelist - set cmd [concat | git-diff-tree -r -s --stdin -- $paths] + if {$gdttype eq "touching paths:"} { + if {[catch {set paths [shellsplit $highlight_files]}]} return + set highlight_paths [makepatterns $paths] + highlight_filelist + set gdtargs [concat -- $paths] + } else { + set gdtargs [list "-S$highlight_files"] + } + set cmd [concat | git-diff-tree -r -s --stdin $gdtargs] set filehighlight [open $cmd r+] fconfigure $filehighlight -blocking 0 fileevent $filehighlight readable readfhighlight + set fhl_list {} drawvisible flushhighlights } proc flushhighlights {} { - global filehighlight + global filehighlight fhl_list if {[info exists filehighlight]} { + lappend fhl_list {} puts $filehighlight "" flush $filehighlight } } proc askfilehighlight {row id} { - global filehighlight fhighlights + global filehighlight fhighlights fhl_list - set fhighlights($row) 0 + lappend fhl_list $id + set fhighlights($row) -1 puts $filehighlight $id } proc readfhighlight {} { global filehighlight fhighlights commitrow curview mainfont iddrawn - - set n [gets $filehighlight line] - if {$n < 0} { - if {[eof $filehighlight]} { - # strange... - puts "oops, git-diff-tree died" - catch {close $filehighlight} - unset filehighlight + global fhl_list + + while {[gets $filehighlight line] >= 0} { + set line [string trim $line] + set i [lsearch -exact $fhl_list $line] + if {$i < 0} continue + for {set j 0} {$j < $i} {incr j} { + set id [lindex $fhl_list $j] + if {[info exists commitrow($curview,$id)]} { + set fhighlights($commitrow($curview,$id)) 0 + } } - return + set fhl_list [lrange $fhl_list [expr {$i+1}] end] + if {$line eq {}} continue + if {![info exists commitrow($curview,$line)]} continue + set row $commitrow($curview,$line) + if {[info exists iddrawn($line)] && ![ishighlighted $row]} { + bolden $row [concat $mainfont bold] + } + set fhighlights($row) 1 } - set line [string trim $line] - if {$line eq {}} return - if {![info exists commitrow($curview,$line)]} return - set row $commitrow($curview,$line) - if {[info exists iddrawn($line)] && ![ishighlighted $row]} { - bolden $row [concat $mainfont bold] + if {[eof $filehighlight]} { + # strange... + puts "oops, git-diff-tree died" + catch {close $filehighlight} + unset filehighlight } - set fhighlights($row) 1 + next_hlcont } -proc hnames_change {name ix op} { - global highlight_names nhighlights nhl_names mainfont +proc find_change {name ix op} { + global nhighlights mainfont boldnamerows + global findstring findpattern findtype # delete previous highlights, if any - set rows [array names nhighlights] - if {$rows ne {}} { - foreach row $rows { - if {$nhighlights($row) >= 2} { - bolden_name $row $mainfont - } - } - unset nhighlights - unbolden $rows + foreach row $boldnamerows { + bolden_name $row $mainfont } - if {[catch {set nhl_names [shellsplit $highlight_names]}]} { - set nhl_names {} - return + set boldnamerows {} + catch {unset nhighlights} + unbolden + if {$findtype ne "Regexp"} { + set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \ + $findstring] + set findpattern "*$e*" } drawvisible } -proc asknamehighlight {row id} { - global nhl_names nhighlights commitinfo iddrawn mainfont +proc askfindhighlight {row id} { + global nhighlights commitinfo iddrawn mainfont + global findstring findtype findloc findpattern if {![info exists commitinfo($id)]} { getcommit $id } + set info $commitinfo($id) set isbold 0 - set author [lindex $commitinfo($id) 1] - set committer [lindex $commitinfo($id) 3] - foreach name $nhl_names { - set pattern "*$name*" - if {[string match -nocase $pattern $author]} { - set isbold 2 - break + set fldtypes {Headline Author Date Committer CDate Comments} + foreach f $info ty $fldtypes { + if {$findloc ne "All fields" && $findloc ne $ty} { + continue } - if {!$isbold && [string match -nocase $pattern $committer]} { - set isbold 1 + if {$findtype eq "Regexp"} { + set doesmatch [regexp $findstring $f] + } elseif {$findtype eq "IgnCase"} { + set doesmatch [string match -nocase $findpattern $f] + } else { + set doesmatch [string match $findpattern $f] + } + if {$doesmatch} { + if {$ty eq "Author"} { + set isbold 2 + } else { + set isbold 1 + } } } if {[info exists iddrawn($id)]} { @@ -1910,6 +1995,207 @@ proc asknamehighlight {row id} { set nhighlights($row) $isbold } +proc vrel_change {name ix op} { + global highlight_related + + rhighlight_none + if {$highlight_related ne "None"} { + after idle drawvisible + } +} + +# prepare for testing whether commits are descendents or ancestors of a +proc rhighlight_sel {a} { + global descendent desc_todo ancestor anc_todo + global highlight_related rhighlights + + catch {unset descendent} + set desc_todo [list $a] + catch {unset ancestor} + set anc_todo [list $a] + if {$highlight_related ne "None"} { + rhighlight_none + after idle drawvisible + } +} + +proc rhighlight_none {} { + global rhighlights + + catch {unset rhighlights} + unbolden +} + +proc is_descendent {a} { + global curview children commitrow descendent desc_todo + + set v $curview + set la $commitrow($v,$a) + set todo $desc_todo + set leftover {} + set done 0 + for {set i 0} {$i < [llength $todo]} {incr i} { + set do [lindex $todo $i] + if {$commitrow($v,$do) < $la} { + lappend leftover $do + continue + } + foreach nk $children($v,$do) { + if {![info exists descendent($nk)]} { + set descendent($nk) 1 + lappend todo $nk + if {$nk eq $a} { + set done 1 + } + } + } + if {$done} { + set desc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]] + return + } + } + set descendent($a) 0 + set desc_todo $leftover +} + +proc is_ancestor {a} { + global curview parentlist commitrow ancestor anc_todo + + set v $curview + set la $commitrow($v,$a) + set todo $anc_todo + set leftover {} + set done 0 + for {set i 0} {$i < [llength $todo]} {incr i} { + set do [lindex $todo $i] + if {![info exists commitrow($v,$do)] || $commitrow($v,$do) > $la} { + lappend leftover $do + continue + } + foreach np [lindex $parentlist $commitrow($v,$do)] { + if {![info exists ancestor($np)]} { + set ancestor($np) 1 + lappend todo $np + if {$np eq $a} { + set done 1 + } + } + } + if {$done} { + set anc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]] + return + } + } + set ancestor($a) 0 + set anc_todo $leftover +} + +proc askrelhighlight {row id} { + global descendent highlight_related iddrawn mainfont rhighlights + global selectedline ancestor + + if {![info exists selectedline]} return + set isbold 0 + if {$highlight_related eq "Descendent" || + $highlight_related eq "Not descendent"} { + if {![info exists descendent($id)]} { + is_descendent $id + } + if {$descendent($id) == ($highlight_related eq "Descendent")} { + set isbold 1 + } + } elseif {$highlight_related eq "Ancestor" || + $highlight_related eq "Not ancestor"} { + if {![info exists ancestor($id)]} { + is_ancestor $id + } + if {$ancestor($id) == ($highlight_related eq "Ancestor")} { + set isbold 1 + } + } + if {[info exists iddrawn($id)]} { + if {$isbold && ![ishighlighted $row]} { + bolden $row [concat $mainfont bold] + } + } + set rhighlights($row) $isbold +} + +proc next_hlcont {} { + global fhl_row fhl_dirn displayorder numcommits + global vhighlights fhighlights nhighlights rhighlights + global hlview filehighlight findstring highlight_related + + if {![info exists fhl_dirn] || $fhl_dirn == 0} return + set row $fhl_row + while {1} { + if {$row < 0 || $row >= $numcommits} { + bell + set fhl_dirn 0 + return + } + set id [lindex $displayorder $row] + if {[info exists hlview]} { + if {![info exists vhighlights($row)]} { + askvhighlight $row $id + } + if {$vhighlights($row) > 0} break + } + if {$findstring ne {}} { + if {![info exists nhighlights($row)]} { + askfindhighlight $row $id + } + if {$nhighlights($row) > 0} break + } + if {$highlight_related ne "None"} { + if {![info exists rhighlights($row)]} { + askrelhighlight $row $id + } + if {$rhighlights($row) > 0} break + } + if {[info exists filehighlight]} { + if {![info exists fhighlights($row)]} { + # ask for a few more while we're at it... + set r $row + for {set n 0} {$n < 100} {incr n} { + if {![info exists fhighlights($r)]} { + askfilehighlight $r [lindex $displayorder $r] + } + incr r $fhl_dirn + if {$r < 0 || $r >= $numcommits} break + } + flushhighlights + } + if {$fhighlights($row) < 0} { + set fhl_row $row + return + } + if {$fhighlights($row) > 0} break + } + incr row $fhl_dirn + } + set fhl_dirn 0 + selectline $row 1 +} + +proc next_highlight {dirn} { + global selectedline fhl_row fhl_dirn + global hlview filehighlight findstring highlight_related + + if {![info exists selectedline]} return + if {!([info exists hlview] || $findstring ne {} || + $highlight_related ne "None" || [info exists filehighlight])} return + set fhl_row [expr {$selectedline + $dirn}] + set fhl_dirn $dirn + next_hlcont +} + +proc cancel_next_highlight {} { + global fhl_dirn + + set fhl_dirn 0 +} + # Graph layout functions proc shortids {ids} { @@ -2084,20 +2370,38 @@ proc visiblerows {} { return [list $r0 $r1] } -proc layoutmore {} { +proc layoutmore {tmax} { global rowlaidout rowoptim commitidx numcommits optim_delay global uparrowlen curview - set row $rowlaidout - set rowlaidout [layoutrows $row $commitidx($curview) 0] - set orow [expr {$rowlaidout - $uparrowlen - 1}] - if {$orow > $rowoptim} { - optimize_rows $rowoptim 0 $orow - set rowoptim $orow - } - set canshow [expr {$rowoptim - $optim_delay}] - if {$canshow > $numcommits} { - showstuff $canshow + while {1} { + if {$rowoptim - $optim_delay > $numcommits} { + showstuff [expr {$rowoptim - $optim_delay}] + } elseif {$rowlaidout - $uparrowlen - 1 > $rowoptim} { + set nr [expr {$rowlaidout - $uparrowlen - 1 - $rowoptim}] + if {$nr > 100} { + set nr 100 + } + optimize_rows $rowoptim 0 [expr {$rowoptim + $nr}] + incr rowoptim $nr + } elseif {$commitidx($curview) > $rowlaidout} { + set nr [expr {$commitidx($curview) - $rowlaidout}] + # may need to increase this threshold if uparrowlen or + # mingaplen are increased... + if {$nr > 150} { + set nr 150 + } + set row $rowlaidout + set rowlaidout [layoutrows $row [expr {$row + $nr}] 0] + if {$rowlaidout == $row} { + return 0 + } + } else { + return 0 + } + if {$tmax ne {} && [clock clicks -milliseconds] >= $tmax} { + return 1 + } } } @@ -2625,11 +2929,11 @@ proc drawlines {id} { } proc drawcmittext {id row col rmx} { - global linespc canv canv2 canv3 canvy0 + global linespc canv canv2 canv3 canvy0 fgcolor global commitlisted commitinfo rowidlist global rowtextx idpos idtags idheads idotherrefs global linehtag linentag linedtag - global mainfont canvxmax + global mainfont canvxmax boldrows boldnamerows fgcolor set ofill [expr {[lindex $commitlisted $row]? "blue": "white"}] set x [xc $row $col] @@ -2637,7 +2941,7 @@ proc drawcmittext {id row col rmx} { set orad [expr {$linespc / 3}] set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \ [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \ - -fill $ofill -outline black -width 1] + -fill $ofill -outline $fgcolor -width 1 -tags circle] $canv raise $t $canv bind $t <1> {selcanvline {} %x %y} set xt [xc $row [llength [lindex $rowidlist $row]]] @@ -2658,18 +2962,20 @@ proc drawcmittext {id row col rmx} { set nfont $mainfont set isbold [ishighlighted $row] if {$isbold > 0} { + lappend boldrows $row lappend font bold if {$isbold > 1} { + lappend boldnamerows $row lappend nfont bold } } - set linehtag($row) [$canv create text $xt $y -anchor w \ - -text $headline -font $font] + set linehtag($row) [$canv create text $xt $y -anchor w -fill $fgcolor \ + -text $headline -font $font -tags text] $canv bind $linehtag($row) "rowmenu %X %Y $id" - set linentag($row) [$canv2 create text 3 $y -anchor w \ - -text $name -font $nfont] - set linedtag($row) [$canv3 create text 3 $y -anchor w \ - -text $date -font $mainfont] + set linentag($row) [$canv2 create text 3 $y -anchor w -fill $fgcolor \ + -text $name -font $nfont -tags text] + set linedtag($row) [$canv3 create text 3 $y -anchor w -fill $fgcolor \ + -text $date -font $mainfont -tags text] set xr [expr {$xt + [font measure $mainfont $headline]}] if {$xr > $canvxmax} { set canvxmax $xr @@ -2681,8 +2987,9 @@ proc drawcmitrow {row} { global displayorder rowidlist global idrangedrawn iddrawn global commitinfo parentlist numcommits - global filehighlight fhighlights nhl_names nhighlights + global filehighlight fhighlights findstring nhighlights global hlview vhighlights + global highlight_related rhighlights if {$row >= $numcommits} return foreach id [lindex $rowidlist $row] { @@ -2709,8 +3016,11 @@ proc drawcmitrow {row} { if {[info exists filehighlight] && ![info exists fhighlights($row)]} { askfilehighlight $row $id } - if {$nhl_names ne {} && ![info exists nhighlights($row)]} { - asknamehighlight $row $id + if {$findstring ne {} && ![info exists nhighlights($row)]} { + askfindhighlight $row $id + } + if {$highlight_related ne "None" && ![info exists rhighlights($row)]} { + askrelhighlight $row $id } if {[info exists iddrawn($id)]} return set col [lsearch -exact [lindex $rowidlist $row] $id] @@ -2760,7 +3070,7 @@ proc drawvisible {} { proc clear_display {} { global iddrawn idrangedrawn - global vhighlights fhighlights nhighlights + global vhighlights fhighlights nhighlights rhighlights allcanvs delete all catch {unset iddrawn} @@ -2768,6 +3078,7 @@ proc clear_display {} { catch {unset vhighlights} catch {unset fhighlights} catch {unset nhighlights} + catch {unset rhighlights} } proc findcrossings {id} { @@ -2884,9 +3195,9 @@ proc bindline {t id} { } proc drawtags {id x xt y1} { - global idtags idheads idotherrefs + global idtags idheads idotherrefs mainhead global linespc lthickness - global canv mainfont commitrow rowtextx curview + global canv mainfont commitrow rowtextx curview fgcolor bgcolor set marks {} set ntags 0 @@ -2911,8 +3222,14 @@ proc drawtags {id x xt y1} { set yb [expr {$yt + $linespc - 1}] set xvals {} set wvals {} + set i -1 foreach tag $marks { - set wid [font measure $mainfont $tag] + incr i + if {$i >= $ntags && $i < $ntags + $nheads && $tag eq $mainhead} { + set wid [font measure [concat $mainfont bold] $tag] + } else { + set wid [font measure $mainfont $tag] + } lappend xvals $xt lappend wvals $wid set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}] @@ -2923,6 +3240,7 @@ proc drawtags {id x xt y1} { foreach tag $marks x $xvals wid $wvals { set xl [expr {$x + $delta}] set xr [expr {$x + $delta + $wid + $lthickness}] + set font $mainfont if {[incr ntags -1] >= 0} { # draw a tag set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \ @@ -2934,6 +3252,9 @@ proc drawtags {id x xt y1} { # draw a head or other ref if {[incr nheads -1] >= 0} { set col green + if {$tag eq $mainhead} { + lappend font bold + } } else { set col "#ddddff" } @@ -2949,10 +3270,12 @@ proc drawtags {id x xt y1} { -width 0 -fill "#ffddaa" -tags tag.$id } } - set t [$canv create text $xl $y1 -anchor w -text $tag \ - -font $mainfont -tags tag.$id] + set t [$canv create text $xl $y1 -anchor w -text $tag -fill $fgcolor \ + -font $font -tags [list tag.$id text]] if {$ntags >= 0} { $canv bind $t <1> [list showtag $tag 1] + } elseif {$nheads >= 0} { + $canv bind $t [list headmenu %X %Y $id $tag] } } return $xt @@ -2971,16 +3294,16 @@ proc xcoord {i level ln} { } proc show_status {msg} { - global canv mainfont + global canv mainfont fgcolor clear_display - $canv create text 3 3 -anchor nw -text $msg -font $mainfont -tags textitems + $canv create text 3 3 -anchor nw -text $msg -font $mainfont \ + -tags text -fill $fgcolor } proc finishcommits {} { global commitidx phase curview - global canv mainfont ctext maincursor textcursor - global findinprogress pending_select + global pending_select if {$commitidx($curview) > 0} { drawrest @@ -2991,6 +3314,108 @@ proc finishcommits {} { catch {unset pending_select} } +# Insert a new commit as the child of the commit on row $row. +# The new commit will be displayed on row $row and the commits +# on that row and below will move down one row. +proc insertrow {row newcmit} { + global displayorder parentlist childlist commitlisted + global commitrow curview rowidlist rowoffsets numcommits + global rowrangelist idrowranges rowlaidout rowoptim numcommits + global linesegends selectedline + + if {$row >= $numcommits} { + puts "oops, inserting new row $row but only have $numcommits rows" + return + } + set p [lindex $displayorder $row] + set displayorder [linsert $displayorder $row $newcmit] + set parentlist [linsert $parentlist $row $p] + set kids [lindex $childlist $row] + lappend kids $newcmit + lset childlist $row $kids + set childlist [linsert $childlist $row {}] + set commitlisted [linsert $commitlisted $row 1] + set l [llength $displayorder] + for {set r $row} {$r < $l} {incr r} { + set id [lindex $displayorder $r] + set commitrow($curview,$id) $r + } + + set idlist [lindex $rowidlist $row] + set offs [lindex $rowoffsets $row] + set newoffs {} + foreach x $idlist { + if {$x eq {} || ($x eq $p && [llength $kids] == 1)} { + lappend newoffs {} + } else { + lappend newoffs 0 + } + } + if {[llength $kids] == 1} { + set col [lsearch -exact $idlist $p] + lset idlist $col $newcmit + } else { + set col [llength $idlist] + lappend idlist $newcmit + lappend offs {} + lset rowoffsets $row $offs + } + set rowidlist [linsert $rowidlist $row $idlist] + set rowoffsets [linsert $rowoffsets [expr {$row+1}] $newoffs] + + set rowrangelist [linsert $rowrangelist $row {}] + set l [llength $rowrangelist] + for {set r 0} {$r < $l} {incr r} { + set ranges [lindex $rowrangelist $r] + if {$ranges ne {} && [lindex $ranges end] >= $row} { + set newranges {} + foreach x $ranges { + if {$x >= $row} { + lappend newranges [expr {$x + 1}] + } else { + lappend newranges $x + } + } + lset rowrangelist $r $newranges + } + } + if {[llength $kids] > 1} { + set rp1 [expr {$row + 1}] + set ranges [lindex $rowrangelist $rp1] + if {$ranges eq {}} { + set ranges [list $row $rp1] + } elseif {[lindex $ranges end-1] == $rp1} { + lset ranges end-1 $row + } + lset rowrangelist $rp1 $ranges + } + foreach id [array names idrowranges] { + set ranges $idrowranges($id) + if {$ranges ne {} && [lindex $ranges end] >= $row} { + set newranges {} + foreach x $ranges { + if {$x >= $row} { + lappend newranges [expr {$x + 1}] + } else { + lappend newranges $x + } + } + set idrowranges($id) $newranges + } + } + + set linesegends [linsert $linesegends $row {}] + + incr rowlaidout + incr rowoptim + incr numcommits + + if {[info exists selectedline] && $selectedline >= $row} { + incr selectedline + } + redisplay +} + # Don't change the text pane cursor if it is currently the hand cursor, # showing that we are over a sha1 ID link. proc settextcursor {c} { @@ -3023,9 +3448,7 @@ proc notbusy {what} { } proc drawrest {} { - global numcommits global startmsecs - global canvy0 numcommits linespc global rowlaidout commitidx curview global pending_select @@ -3039,6 +3462,7 @@ proc drawrest {} { } set drawmsecs [expr {[clock clicks -milliseconds] - $startmsecs}] + #global numcommits #puts "overall $drawmsecs ms for $numcommits commits" } @@ -3071,12 +3495,9 @@ proc dofind {} { stopfindproc unmarkmatches + cancel_next_highlight focus . set matchinglines {} - if {$findloc == "Pickaxe"} { - findpatches - return - } if {$findtype == "IgnCase"} { set foundstring [string tolower $findstring] } else { @@ -3086,17 +3507,13 @@ proc dofind {} { if {$foundstrlen == 0} return regsub -all {[*?\[\\]} $foundstring {\\&} matchstring set matchstring "*$matchstring*" - if {$findloc == "Files"} { - findfiles - return - } if {![info exists selectedline]} { set oldsel -1 } else { set oldsel $selectedline } set didsel 0 - set fldtypes {Headline Author Date Committer CDate Comment} + set fldtypes {Headline Author Date Committer CDate Comments} set l -1 foreach id $displayorder { set d $commitdata($id) @@ -3199,18 +3616,6 @@ proc findprev {} { } } -proc findlocchange {name ix op} { - global findloc findtype findtypemenu - if {$findloc == "Pickaxe"} { - set findtype Exact - set state disabled - } else { - set state normal - } - $findtypemenu entryconf 1 -state $state - $findtypemenu entryconf 2 -state $state -} - proc stopfindproc {{done 0}} { global findprocpid findprocfile findids global ctext findoldcursor phase maincursor textcursor @@ -3228,300 +3633,59 @@ proc stopfindproc {{done 0}} { notbusy find } -proc findpatches {} { - global findstring selectedline numcommits - global findprocpid findprocfile - global finddidsel ctext displayorder findinprogress - global findinsertpos - - if {$numcommits == 0} return - - # make a list of all the ids to search, starting at the one - # after the selected line (if any) - if {[info exists selectedline]} { - set l $selectedline - } else { - set l -1 - } - set inputids {} - for {set i 0} {$i < $numcommits} {incr i} { - if {[incr l] >= $numcommits} { - set l 0 - } - append inputids [lindex $displayorder $l] "\n" - } - - if {[catch { - set f [open [list | git-diff-tree --stdin -s -r -S$findstring \ - << $inputids] r] - } err]} { - error_popup "Error starting search process: $err" - return - } +# mark a commit as matching by putting a yellow background +# behind the headline +proc markheadline {l id} { + global canv mainfont linehtag - set findinsertpos end - set findprocfile $f - set findprocpid [pid $f] - fconfigure $f -blocking 0 - fileevent $f readable readfindproc - set finddidsel 0 - nowbusy find - set findinprogress 1 + drawcmitrow $l + set bbox [$canv bbox $linehtag($l)] + set t [$canv create rect $bbox -outline {} -tags matches -fill yellow] + $canv lower $t } -proc readfindproc {} { - global findprocfile finddidsel - global commitrow matchinglines findinsertpos curview - - set n [gets $findprocfile line] - if {$n < 0} { - if {[eof $findprocfile]} { - stopfindproc 1 - if {!$finddidsel} { - bell - } - } - return - } - if {![regexp {^[0-9a-f]{40}} $line id]} { - error_popup "Can't parse git-diff-tree output: $line" - stopfindproc - return - } - if {![info exists commitrow($curview,$id)]} { - puts stderr "spurious id: $id" - return +# mark the bits of a headline, author or date that match a find string +proc markmatches {canv l str tag matches font} { + set bbox [$canv bbox $tag] + set x0 [lindex $bbox 0] + set y0 [lindex $bbox 1] + set y1 [lindex $bbox 3] + foreach match $matches { + set start [lindex $match 0] + set end [lindex $match 1] + if {$start > $end} continue + set xoff [font measure $font [string range $str 0 [expr {$start-1}]]] + set xlen [font measure $font [string range $str 0 [expr {$end}]]] + set t [$canv create rect [expr {$x0+$xoff}] $y0 \ + [expr {$x0+$xlen+2}] $y1 \ + -outline {} -tags matches -fill yellow] + $canv lower $t } - set l $commitrow($curview,$id) - insertmatch $l $id } -proc insertmatch {l id} { - global matchinglines findinsertpos finddidsel +proc unmarkmatches {} { + global matchinglines findids + allcanvs delete matches + catch {unset matchinglines} + catch {unset findids} +} - if {$findinsertpos == "end"} { - if {$matchinglines != {} && $l < [lindex $matchinglines 0]} { - set matchinglines [linsert $matchinglines 0 $l] - set findinsertpos 1 - } else { - lappend matchinglines $l - } - } else { - set matchinglines [linsert $matchinglines $findinsertpos $l] - incr findinsertpos +proc selcanvline {w x y} { + global canv canvy0 ctext linespc + global rowtextx + set ymax [lindex [$canv cget -scrollregion] 3] + if {$ymax == {}} return + set yfrac [lindex [$canv yview] 0] + set y [expr {$y + $yfrac * $ymax}] + set l [expr {int(($y - $canvy0) / $linespc + 0.5)}] + if {$l < 0} { + set l 0 } - markheadline $l $id - if {!$finddidsel} { - findselectline $l - set finddidsel 1 + if {$w eq $canv} { + if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return } -} - -proc findfiles {} { - global selectedline numcommits displayorder ctext - global ffileline finddidsel parentlist - global findinprogress findstartline findinsertpos - global treediffs fdiffid fdiffsneeded fdiffpos - global findmergefiles - - if {$numcommits == 0} return - - if {[info exists selectedline]} { - set l [expr {$selectedline + 1}] - } else { - set l 0 - } - set ffileline $l - set findstartline $l - set diffsneeded {} - set fdiffsneeded {} - while 1 { - set id [lindex $displayorder $l] - if {$findmergefiles || [llength [lindex $parentlist $l]] == 1} { - if {![info exists treediffs($id)]} { - append diffsneeded "$id\n" - lappend fdiffsneeded $id - } - } - if {[incr l] >= $numcommits} { - set l 0 - } - if {$l == $findstartline} break - } - - # start off a git-diff-tree process if needed - if {$diffsneeded ne {}} { - if {[catch { - set df [open [list | git-diff-tree -r --stdin << $diffsneeded] r] - } err ]} { - error_popup "Error starting search process: $err" - return - } - catch {unset fdiffid} - set fdiffpos 0 - fconfigure $df -blocking 0 - fileevent $df readable [list readfilediffs $df] - } - - set finddidsel 0 - set findinsertpos end - set id [lindex $displayorder $l] - nowbusy find - set findinprogress 1 - findcont - update -} - -proc readfilediffs {df} { - global findid fdiffid fdiffs - - set n [gets $df line] - if {$n < 0} { - if {[eof $df]} { - donefilediff - if {[catch {close $df} err]} { - stopfindproc - bell - error_popup "Error in git-diff-tree: $err" - } elseif {[info exists findid]} { - set id $findid - stopfindproc - bell - error_popup "Couldn't find diffs for $id" - } - } - return - } - if {[regexp {^([0-9a-f]{40})$} $line match id]} { - # start of a new string of diffs - donefilediff - set fdiffid $id - set fdiffs {} - } elseif {[string match ":*" $line]} { - lappend fdiffs [lindex $line 5] - } -} - -proc donefilediff {} { - global fdiffid fdiffs treediffs findid - global fdiffsneeded fdiffpos - - if {[info exists fdiffid]} { - while {[lindex $fdiffsneeded $fdiffpos] ne $fdiffid - && $fdiffpos < [llength $fdiffsneeded]} { - # git-diff-tree doesn't output anything for a commit - # which doesn't change anything - set nullid [lindex $fdiffsneeded $fdiffpos] - set treediffs($nullid) {} - if {[info exists findid] && $nullid eq $findid} { - unset findid - findcont - } - incr fdiffpos - } - incr fdiffpos - - if {![info exists treediffs($fdiffid)]} { - set treediffs($fdiffid) $fdiffs - } - if {[info exists findid] && $fdiffid eq $findid} { - unset findid - findcont - } - } -} - -proc findcont {} { - global findid treediffs parentlist - global ffileline findstartline finddidsel - global displayorder numcommits matchinglines findinprogress - global findmergefiles - - set l $ffileline - while {1} { - set id [lindex $displayorder $l] - if {$findmergefiles || [llength [lindex $parentlist $l]] == 1} { - if {![info exists treediffs($id)]} { - set findid $id - set ffileline $l - return - } - set doesmatch 0 - foreach f $treediffs($id) { - set x [findmatches $f] - if {$x != {}} { - set doesmatch 1 - break - } - } - if {$doesmatch} { - insertmatch $l $id - } - } - if {[incr l] >= $numcommits} { - set l 0 - } - if {$l == $findstartline} break - } - stopfindproc - if {!$finddidsel} { - bell - } -} - -# mark a commit as matching by putting a yellow background -# behind the headline -proc markheadline {l id} { - global canv mainfont linehtag - - drawcmitrow $l - set bbox [$canv bbox $linehtag($l)] - set t [$canv create rect $bbox -outline {} -tags matches -fill yellow] - $canv lower $t -} - -# mark the bits of a headline, author or date that match a find string -proc markmatches {canv l str tag matches font} { - set bbox [$canv bbox $tag] - set x0 [lindex $bbox 0] - set y0 [lindex $bbox 1] - set y1 [lindex $bbox 3] - foreach match $matches { - set start [lindex $match 0] - set end [lindex $match 1] - if {$start > $end} continue - set xoff [font measure $font [string range $str 0 [expr {$start-1}]]] - set xlen [font measure $font [string range $str 0 [expr {$end}]]] - set t [$canv create rect [expr {$x0+$xoff}] $y0 \ - [expr {$x0+$xlen+2}] $y1 \ - -outline {} -tags matches -fill yellow] - $canv lower $t - } -} - -proc unmarkmatches {} { - global matchinglines findids - allcanvs delete matches - catch {unset matchinglines} - catch {unset findids} -} - -proc selcanvline {w x y} { - global canv canvy0 ctext linespc - global rowtextx - set ymax [lindex [$canv cget -scrollregion] 3] - if {$ymax == {}} return - set yfrac [lindex [$canv yview] 0] - set y [expr {$y + $yfrac * $ymax}] - set l [expr {int(($y - $canvy0) / $linespc + 0.5)}] - if {$l < 0} { - set l 0 - } - if {$w eq $canv} { - if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return - } - unmarkmatches - selectline $l 1 + unmarkmatches + selectline $l 1 } proc commit_descriptor {p} { @@ -3533,17 +3697,16 @@ proc commit_descriptor {p} { if {[llength $commitinfo($p)] > 1} { set l [lindex $commitinfo($p) 0] } - return "$p ($l)" + return "$p ($l)\n" } # append some text to the ctext widget, and make any SHA1 ID # that we know about be a clickable link. -proc appendwithlinks {text} { +proc appendwithlinks {text tags} { global ctext commitrow linknum curview set start [$ctext index "end - 1c"] - $ctext insert end $text - $ctext insert end "\n" + $ctext insert end $text $tags set links [regexp -indices -all -inline {[0-9a-f]{40}} $text] foreach l $links { set s [lindex $l 0] @@ -3578,6 +3741,69 @@ proc viewnextline {dir} { allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}] } +# add a list of tag or branch names at position pos +# returns the number of names inserted +proc appendrefs {pos tags var} { + global ctext commitrow linknum curview $var + + if {[catch {$ctext index $pos}]} { + return 0 + } + set tags [lsort $tags] + set sep {} + foreach tag $tags { + set id [set $var\($tag\)] + set lk link$linknum + incr linknum + $ctext insert $pos $sep + $ctext insert $pos $tag $lk + $ctext tag conf $lk -foreground blue + if {[info exists commitrow($curview,$id)]} { + $ctext tag bind $lk <1> \ + [list selectline $commitrow($curview,$id) 1] + $ctext tag conf $lk -underline 1 + $ctext tag bind $lk { %W configure -cursor hand2 } + $ctext tag bind $lk { %W configure -cursor $curtextcursor } + } + set sep ", " + } + return [llength $tags] +} + +proc taglist {ids} { + global idtags + + set tags {} + foreach id $ids { + foreach tag $idtags($id) { + lappend tags $tag + } + } + return $tags +} + +# called when we have finished computing the nearby tags +proc dispneartags {} { + global selectedline currentid ctext anc_tags desc_tags showneartags + global desc_heads + + if {![info exists selectedline] || !$showneartags} return + set id $currentid + $ctext conf -state normal + if {[info exists desc_heads($id)]} { + if {[appendrefs branch $desc_heads($id) headids] > 1} { + $ctext insert "branch -2c" "es" + } + } + if {[info exists anc_tags($id)]} { + appendrefs follows [taglist $anc_tags($id)] tagids + } + if {[info exists desc_tags($id)]} { + appendrefs precedes [taglist $desc_tags($id)] tagids + } + $ctext conf -state disabled +} + proc selectline {l isnew} { global canv canv2 canv3 ctext commitinfo selectedline global displayorder linehtag linentag linedtag @@ -3585,11 +3811,12 @@ proc selectline {l isnew} { global currentid sha1entry global commentend idtags linknum global mergemax numcommits pending_select - global cmitmode + global cmitmode desc_tags anc_tags showneartags allcommits desc_heads catch {unset pending_select} $canv delete hover normalline + cancel_next_highlight if {$l < 0 || $l >= $numcommits} return set y [expr {$canvy0 + $l * $linespc}] set ymax [lindex [$canv cget -scrollregion] 3] @@ -3653,6 +3880,7 @@ proc selectline {l isnew} { $sha1entry insert 0 $id $sha1entry selection from 0 $sha1entry selection to end + rhighlight_sel $id $ctext conf -state normal clear_ctext @@ -3670,7 +3898,7 @@ proc selectline {l isnew} { $ctext insert end "\n" } - set comment {} + set headers {} set olds [lindex $parentlist $l] if {[llength $olds] > 1} { set np 0 @@ -3681,23 +3909,50 @@ proc selectline {l isnew} { set tag m$np } $ctext insert end "Parent: " $tag - appendwithlinks [commit_descriptor $p] + appendwithlinks [commit_descriptor $p] {} incr np } } else { foreach p $olds { - append comment "Parent: [commit_descriptor $p]\n" + append headers "Parent: [commit_descriptor $p]" } } foreach c [lindex $childlist $l] { - append comment "Child: [commit_descriptor $c]\n" + append headers "Child: [commit_descriptor $c]" } - append comment "\n" - append comment [lindex $info 5] # make anything that looks like a SHA1 ID be a clickable link - appendwithlinks $comment + appendwithlinks $headers {} + if {$showneartags} { + if {![info exists allcommits]} { + getallcommits + } + $ctext insert end "Branch: " + $ctext mark set branch "end -1c" + $ctext mark gravity branch left + if {[info exists desc_heads($id)]} { + if {[appendrefs branch $desc_heads($id) headids] > 1} { + # turn "Branch" into "Branches" + $ctext insert "branch -2c" "es" + } + } + $ctext insert end "\nFollows: " + $ctext mark set follows "end -1c" + $ctext mark gravity follows left + if {[info exists anc_tags($id)]} { + appendrefs follows [taglist $anc_tags($id)] tagids + } + $ctext insert end "\nPrecedes: " + $ctext mark set precedes "end -1c" + $ctext mark gravity precedes left + if {[info exists desc_tags($id)]} { + appendrefs precedes [taglist $desc_tags($id)] tagids + } + $ctext insert end "\n" + } + $ctext insert end "\n" + appendwithlinks [lindex $info 5] {comment} $ctext tag delete Comments $ctext tag remove found 1.0 end @@ -3760,6 +4015,8 @@ proc unselectline {} { catch {unset selectedline} catch {unset currentid} allcanvs delete secsel + rhighlight_none + cancel_next_highlight } proc reselectline {} { @@ -3838,7 +4095,7 @@ proc gettree {id} { catch {unset diffmergeid} if {![info exists treefilelist($id)]} { if {![info exists treepending]} { - if {[catch {set gtf [open [concat | git-ls-tree -r $id] r]}]} { + if {[catch {set gtf [open [concat | git ls-tree -r $id] r]}]} { return } set treepending $id @@ -3886,7 +4143,7 @@ proc showfile {f} { return } set blob [lindex $treeidlist($diffids) $i] - if {[catch {set bf [open [concat | git-cat-file blob $blob] r]} err]} { + if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} { puts "oops, error reading blob $blob: $err" return } @@ -3928,7 +4185,7 @@ proc mergediff {id l} { set diffids $id # this doesn't seem to actually affect anything... set env(GIT_DIFF_OPTS) $diffopts - set cmd [concat | git-diff-tree --no-commit-id --cc $id] + set cmd [concat | git diff-tree --no-commit-id --cc $id] if {[catch {set mdf [open $cmd r]} err]} { error_popup "Error getting merge diffs: $err" return @@ -4040,7 +4297,7 @@ proc gettreediffs {ids} { set treepending $ids set treediff {} if {[catch \ - {set gdtf [open [concat | git-diff-tree --no-commit-id -r $ids] r]} \ + {set gdtf [open [concat | git diff-tree --no-commit-id -r $ids] r]} \ ]} return fconfigure $gdtf -blocking 0 fileevent $gdtf readable [list gettreediffline $gdtf $ids] @@ -4076,7 +4333,7 @@ proc getblobdiffs {ids} { global nextupdate diffinhdr treediffs set env(GIT_DIFF_OPTS) $diffopts - set cmd [concat | git-diff-tree --no-commit-id -r -p -C $ids] + set cmd [concat | git diff-tree --no-commit-id -r -p -C $ids] if {[catch {set bdf [open $cmd r]} err]} { puts "error getting diffs: $err" return @@ -4183,12 +4440,27 @@ proc getblobdiffline {bdf ids} { } } +proc prevfile {} { + global difffilestart ctext + set prev [lindex $difffilestart 0] + set here [$ctext index @0,0] + foreach loc $difffilestart { + if {[$ctext compare $loc >= $here]} { + $ctext yview $prev + return + } + set prev $loc + } + $ctext yview $prev +} + proc nextfile {} { global difffilestart ctext set here [$ctext index @0,0] foreach loc $difffilestart { if {[$ctext compare $loc > $here]} { $ctext yview $loc + return } } } @@ -4196,41 +4468,90 @@ proc nextfile {} { proc clear_ctext {{first 1.0}} { global ctext smarktop smarkbot - if {![info exists smarktop] || [$ctext compare $first < $smarktop]} { - set smarktop $first + set l [lindex [split $first .] 0] + if {![info exists smarktop] || [$ctext compare $first < $smarktop.0]} { + set smarktop $l } - if {![info exists smarkbot] || [$ctext compare $first < $smarkbot]} { - set smarkbot $first + if {![info exists smarkbot] || [$ctext compare $first < $smarkbot.0]} { + set smarkbot $l } $ctext delete $first end } proc incrsearch {name ix op} { - global ctext searchstring + global ctext searchstring searchdirn $ctext tag remove found 1.0 end + if {[catch {$ctext index anchor}]} { + # no anchor set, use start of selection, or of visible area + set sel [$ctext tag ranges sel] + if {$sel ne {}} { + $ctext mark set anchor [lindex $sel 0] + } elseif {$searchdirn eq "-forwards"} { + $ctext mark set anchor @0,0 + } else { + $ctext mark set anchor @0,[winfo height $ctext] + } + } if {$searchstring ne {}} { + set here [$ctext search $searchdirn -- $searchstring anchor] + if {$here ne {}} { + $ctext see $here + } searchmarkvisible 1 } } proc dosearch {} { - global sstring ctext searchstring + global sstring ctext searchstring searchdirn focus $sstring $sstring icursor end - $ctext tag remove sel 1.0 end - if {$searchstring eq {}} return - set here [$ctext index insert] - set match [$ctext search -count mlen -- $searchstring $here] - if {$match eq {}} { - bell - return + set searchdirn -forwards + if {$searchstring ne {}} { + set sel [$ctext tag ranges sel] + if {$sel ne {}} { + set start "[lindex $sel 0] + 1c" + } elseif {[catch {set start [$ctext index anchor]}]} { + set start "@0,0" + } + set match [$ctext search -count mlen -- $searchstring $start] + $ctext tag remove sel 1.0 end + if {$match eq {}} { + bell + return + } + $ctext see $match + set mend "$match + $mlen c" + $ctext tag add sel $match $mend + $ctext mark unset anchor + } +} + +proc dosearchback {} { + global sstring ctext searchstring searchdirn + + focus $sstring + $sstring icursor end + set searchdirn -backwards + if {$searchstring ne {}} { + set sel [$ctext tag ranges sel] + if {$sel ne {}} { + set start [lindex $sel 0] + } elseif {[catch {set start [$ctext index anchor]}]} { + set start @0,[winfo height $ctext] + } + set match [$ctext search -backwards -count ml -- $searchstring $start] + $ctext tag remove sel 1.0 end + if {$match eq {}} { + bell + return + } + $ctext see $match + set mend "$match + $ml c" + $ctext tag add sel $match $mend + $ctext mark unset anchor } - $ctext see $match - set mend "$match + $mlen c" - $ctext tag add sel $match $mend - $ctext mark set insert $mend } proc searchmark {first last} { @@ -4268,7 +4589,7 @@ proc searchmarkvisible {doall} { } proc scrolltext {f0 f1} { - global ctext smarktop smarkbot searchstring + global searchstring .ctop.cdet.left.sb set $f0 $f1 if {$searchstring ne {}} { @@ -4302,6 +4623,7 @@ proc redisplay {} { drawvisible if {[info exists selectedline]} { selectline $selectedline 0 + allcanvs yview moveto [lindex $span 0] } } @@ -4445,7 +4767,8 @@ proc linehover {} { set t [$canv create rectangle $x0 $y0 $x1 $y1 \ -fill \#ffff80 -outline black -width 1 -tags hover] $canv raise $t - set t [$canv create text $x $y -anchor nw -text $text -tags hover -font $mainfont] + set t [$canv create text $x $y -anchor nw -text $text -tags hover \ + -font $mainfont] $canv raise $t } @@ -4696,7 +5019,7 @@ proc mkpatchgo {} { set oldid [$patchtop.fromsha1 get] set newid [$patchtop.tosha1 get] set fname [$patchtop.fname get] - if {[catch {exec git-diff-tree -p $oldid $newid >$fname &} err]} { + if {[catch {exec git diff-tree -p $oldid $newid >$fname &} err]} { error_popup "Error creating patch: $err" } catch {destroy $patchtop} @@ -4768,16 +5091,24 @@ proc domktag {} { set tagids($tag) $id lappend idtags($id) $tag redrawtags $id + addedtag $id } proc redrawtags {id} { global canv linehtag commitrow idpos selectedline curview + global mainfont canvxmax if {![info exists commitrow($curview,$id)]} return drawcmitrow $commitrow($curview,$id) $canv delete tag.$id set xt [eval drawtags $id $idpos($id)] $canv coords $linehtag($commitrow($curview,$id)) $xt [lindex $idpos($id) 2] + set text [$canv itemcget $linehtag($commitrow($curview,$id)) -text] + set xr [expr {$xt + [font measure $mainfont $text]}] + if {$xr > $canvxmax} { + set canvxmax $xr + setcanvscroll + } if {[info exists selectedline] && $selectedline == $commitrow($curview,$id)} { selectline $selectedline 0 @@ -4851,26 +5182,537 @@ proc wrcomcan {} { unset wrcomtop } -proc listrefs {id} { - global idtags idheads idotherrefs +proc mkbranch {} { + global rowmenuid mkbrtop - set x {} + set top .makebranch + catch {destroy $top} + toplevel $top + label $top.title -text "Create new branch" + grid $top.title - -pady 10 + label $top.id -text "ID:" + entry $top.sha1 -width 40 -relief flat + $top.sha1 insert 0 $rowmenuid + $top.sha1 conf -state readonly + grid $top.id $top.sha1 -sticky w + label $top.nlab -text "Name:" + entry $top.name -width 40 + grid $top.nlab $top.name -sticky w + frame $top.buts + button $top.buts.go -text "Create" -command [list mkbrgo $top] + button $top.buts.can -text "Cancel" -command "catch {destroy $top}" + grid $top.buts.go $top.buts.can + grid columnconfigure $top.buts 0 -weight 1 -uniform a + grid columnconfigure $top.buts 1 -weight 1 -uniform a + grid $top.buts - -pady 10 -sticky ew + focus $top.name +} + +proc mkbrgo {top} { + global headids idheads + + set name [$top.name get] + set id [$top.sha1 get] + if {$name eq {}} { + error_popup "Please specify a name for the new branch" + return + } + catch {destroy $top} + nowbusy newbranch + update + if {[catch { + exec git branch $name $id + } err]} { + notbusy newbranch + error_popup $err + } else { + addedhead $id $name + # XXX should update list of heads displayed for selected commit + notbusy newbranch + redrawtags $id + } +} + +proc cherrypick {} { + global rowmenuid curview commitrow + global mainhead desc_heads anc_tags desc_tags allparents allchildren + + if {[info exists desc_heads($rowmenuid)] + && [lsearch -exact $desc_heads($rowmenuid) $mainhead] >= 0} { + set ok [confirm_popup "Commit [string range $rowmenuid 0 7] is already\ + included in branch $mainhead -- really re-apply it?"] + if {!$ok} return + } + nowbusy cherrypick + update + set oldhead [exec git rev-parse HEAD] + # Unfortunately git-cherry-pick writes stuff to stderr even when + # no error occurs, and exec takes that as an indication of error... + if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} { + notbusy cherrypick + error_popup $err + return + } + set newhead [exec git rev-parse HEAD] + if {$newhead eq $oldhead} { + notbusy cherrypick + error_popup "No changes committed" + return + } + set allparents($newhead) $oldhead + lappend allchildren($oldhead) $newhead + set desc_heads($newhead) $mainhead + if {[info exists anc_tags($oldhead)]} { + set anc_tags($newhead) $anc_tags($oldhead) + } + set desc_tags($newhead) {} + if {[info exists commitrow($curview,$oldhead)]} { + insertrow $commitrow($curview,$oldhead) $newhead + if {$mainhead ne {}} { + movedhead $newhead $mainhead + } + redrawtags $oldhead + redrawtags $newhead + } + notbusy cherrypick +} + +# context menu for a head +proc headmenu {x y id head} { + global headmenuid headmenuhead headctxmenu + + set headmenuid $id + set headmenuhead $head + tk_popup $headctxmenu $x $y +} + +proc cobranch {} { + global headmenuid headmenuhead mainhead headids + + # check the tree is clean first?? + set oldmainhead $mainhead + nowbusy checkout + update + if {[catch { + exec git checkout $headmenuhead + } err]} { + notbusy checkout + error_popup $err + } else { + notbusy checkout + set mainhead $headmenuhead + if {[info exists headids($oldmainhead)]} { + redrawtags $headids($oldmainhead) + } + redrawtags $headmenuid + } +} + +proc rmbranch {} { + global desc_heads headmenuid headmenuhead mainhead + global headids idheads + + set head $headmenuhead + set id $headmenuid + if {$head eq $mainhead} { + error_popup "Cannot delete the currently checked-out branch" + return + } + if {$desc_heads($id) eq $head} { + # the stuff on this branch isn't on any other branch + if {![confirm_popup "The commits on branch $head aren't on any other\ + branch.\nReally delete branch $head?"]} return + } + nowbusy rmbranch + update + if {[catch {exec git branch -D $head} err]} { + notbusy rmbranch + error_popup $err + return + } + removedhead $id $head + redrawtags $id + notbusy rmbranch +} + +# Stuff for finding nearby tags +proc getallcommits {} { + global allcstart allcommits allcfd allids + + set allids {} + set fd [open [concat | git rev-list --all --topo-order --parents] r] + set allcfd $fd + fconfigure $fd -blocking 0 + set allcommits "reading" + nowbusy allcommits + restartgetall $fd +} + +proc discardallcommits {} { + global allparents allchildren allcommits allcfd + global desc_tags anc_tags alldtags tagisdesc allids desc_heads + + if {![info exists allcommits]} return + if {$allcommits eq "reading"} { + catch {close $allcfd} + } + foreach v {allcommits allchildren allparents allids desc_tags anc_tags + alldtags tagisdesc desc_heads} { + catch {unset $v} + } +} + +proc restartgetall {fd} { + global allcstart + + fileevent $fd readable [list getallclines $fd] + set allcstart [clock clicks -milliseconds] +} + +proc combine_dtags {l1 l2} { + global tagisdesc notfirstd + + set res [lsort -unique [concat $l1 $l2]] + for {set i 0} {$i < [llength $res]} {incr i} { + set x [lindex $res $i] + for {set j [expr {$i+1}]} {$j < [llength $res]} {} { + set y [lindex $res $j] + if {[info exists tagisdesc($x,$y)]} { + if {$tagisdesc($x,$y) > 0} { + # x is a descendent of y, exclude x + set res [lreplace $res $i $i] + incr i -1 + break + } else { + # y is a descendent of x, exclude y + set res [lreplace $res $j $j] + } + } else { + # no relation, keep going + incr j + } + } + } + return $res +} + +proc combine_atags {l1 l2} { + global tagisdesc + + set res [lsort -unique [concat $l1 $l2]] + for {set i 0} {$i < [llength $res]} {incr i} { + set x [lindex $res $i] + for {set j [expr {$i+1}]} {$j < [llength $res]} {} { + set y [lindex $res $j] + if {[info exists tagisdesc($x,$y)]} { + if {$tagisdesc($x,$y) < 0} { + # x is an ancestor of y, exclude x + set res [lreplace $res $i $i] + incr i -1 + break + } else { + # y is an ancestor of x, exclude y + set res [lreplace $res $j $j] + } + } else { + # no relation, keep going + incr j + } + } + } + return $res +} + +proc forward_pass {id children} { + global idtags desc_tags idheads desc_heads alldtags tagisdesc + + set dtags {} + set dheads {} + foreach child $children { + if {[info exists idtags($child)]} { + set ctags [list $child] + } else { + set ctags $desc_tags($child) + } + if {$dtags eq {}} { + set dtags $ctags + } elseif {$ctags ne $dtags} { + set dtags [combine_dtags $dtags $ctags] + } + set cheads $desc_heads($child) + if {$dheads eq {}} { + set dheads $cheads + } elseif {$cheads ne $dheads} { + set dheads [lsort -unique [concat $dheads $cheads]] + } + } + set desc_tags($id) $dtags if {[info exists idtags($id)]} { - set x $idtags($id) + set adt $dtags + foreach tag $dtags { + set adt [concat $adt $alldtags($tag)] + } + set adt [lsort -unique $adt] + set alldtags($id) $adt + foreach tag $adt { + set tagisdesc($id,$tag) -1 + set tagisdesc($tag,$id) 1 + } } - set y {} if {[info exists idheads($id)]} { - set y $idheads($id) + set dheads [concat $dheads $idheads($id)] } - set z {} - if {[info exists idotherrefs($id)]} { - set z $idotherrefs($id) + set desc_heads($id) $dheads +} + +proc getallclines {fd} { + global allparents allchildren allcommits allcstart + global desc_tags anc_tags idtags tagisdesc allids + global idheads travindex + + while {[gets $fd line] >= 0} { + set id [lindex $line 0] + lappend allids $id + set olds [lrange $line 1 end] + set allparents($id) $olds + if {![info exists allchildren($id)]} { + set allchildren($id) {} + } + foreach p $olds { + lappend allchildren($p) $id + } + # compute nearest tagged descendents as we go + # also compute descendent heads + forward_pass $id $allchildren($id) + if {[clock clicks -milliseconds] - $allcstart >= 50} { + fileevent $fd readable {} + after idle restartgetall $fd + return + } + } + if {[eof $fd]} { + set travindex [llength $allids] + set allcommits "traversing" + after idle restartatags + if {[catch {close $fd} err]} { + error_popup "Error reading full commit graph: $err.\n\ + Results may be incomplete." + } + } +} + +# walk backward through the tree and compute nearest tagged ancestors +proc restartatags {} { + global allids allparents idtags anc_tags travindex + + set t0 [clock clicks -milliseconds] + set i $travindex + while {[incr i -1] >= 0} { + set id [lindex $allids $i] + set atags {} + foreach p $allparents($id) { + if {[info exists idtags($p)]} { + set ptags [list $p] + } else { + set ptags $anc_tags($p) + } + if {$atags eq {}} { + set atags $ptags + } elseif {$ptags ne $atags} { + set atags [combine_atags $atags $ptags] + } + } + set anc_tags($id) $atags + if {[clock clicks -milliseconds] - $t0 >= 50} { + set travindex $i + after idle restartatags + return + } + } + set allcommits "done" + set travindex 0 + notbusy allcommits + dispneartags +} + +# update the desc_tags and anc_tags arrays for a new tag just added +proc addedtag {id} { + global desc_tags anc_tags allparents allchildren allcommits + global idtags tagisdesc alldtags + + if {![info exists desc_tags($id)]} return + set adt $desc_tags($id) + foreach t $desc_tags($id) { + set adt [concat $adt $alldtags($t)] + } + set adt [lsort -unique $adt] + set alldtags($id) $adt + foreach t $adt { + set tagisdesc($id,$t) -1 + set tagisdesc($t,$id) 1 + } + if {[info exists anc_tags($id)]} { + set todo $anc_tags($id) + while {$todo ne {}} { + set do [lindex $todo 0] + set todo [lrange $todo 1 end] + if {[info exists tagisdesc($id,$do)]} continue + set tagisdesc($do,$id) -1 + set tagisdesc($id,$do) 1 + if {[info exists anc_tags($do)]} { + set todo [concat $todo $anc_tags($do)] + } + } + } + + set lastold $desc_tags($id) + set lastnew [list $id] + set nup 0 + set nch 0 + set todo $allparents($id) + while {$todo ne {}} { + set do [lindex $todo 0] + set todo [lrange $todo 1 end] + if {![info exists desc_tags($do)]} continue + if {$desc_tags($do) ne $lastold} { + set lastold $desc_tags($do) + set lastnew [combine_dtags $lastold [list $id]] + incr nch + } + if {$lastold eq $lastnew} continue + set desc_tags($do) $lastnew + incr nup + if {![info exists idtags($do)]} { + set todo [concat $todo $allparents($do)] + } + } + + if {![info exists anc_tags($id)]} return + set lastold $anc_tags($id) + set lastnew [list $id] + set nup 0 + set nch 0 + set todo $allchildren($id) + while {$todo ne {}} { + set do [lindex $todo 0] + set todo [lrange $todo 1 end] + if {![info exists anc_tags($do)]} continue + if {$anc_tags($do) ne $lastold} { + set lastold $anc_tags($do) + set lastnew [combine_atags $lastold [list $id]] + incr nch + } + if {$lastold eq $lastnew} continue + set anc_tags($do) $lastnew + incr nup + if {![info exists idtags($do)]} { + set todo [concat $todo $allchildren($do)] + } + } +} + +# update the desc_heads array for a new head just added +proc addedhead {hid head} { + global desc_heads allparents headids idheads + + set headids($head) $hid + lappend idheads($hid) $head + + set todo [list $hid] + while {$todo ne {}} { + set do [lindex $todo 0] + set todo [lrange $todo 1 end] + if {![info exists desc_heads($do)] || + [lsearch -exact $desc_heads($do) $head] >= 0} continue + set oldheads $desc_heads($do) + lappend desc_heads($do) $head + set heads $desc_heads($do) + while {1} { + set p $allparents($do) + if {[llength $p] != 1 || ![info exists desc_heads($p)] || + $desc_heads($p) ne $oldheads} break + set do $p + set desc_heads($do) $heads + } + set todo [concat $todo $p] + } +} + +# update the desc_heads array for a head just removed +proc removedhead {hid head} { + global desc_heads allparents headids idheads + + unset headids($head) + if {$idheads($hid) eq $head} { + unset idheads($hid) + } else { + set i [lsearch -exact $idheads($hid) $head] + if {$i >= 0} { + set idheads($hid) [lreplace $idheads($hid) $i $i] + } + } + + set todo [list $hid] + while {$todo ne {}} { + set do [lindex $todo 0] + set todo [lrange $todo 1 end] + if {![info exists desc_heads($do)]} continue + set i [lsearch -exact $desc_heads($do) $head] + if {$i < 0} continue + set oldheads $desc_heads($do) + set heads [lreplace $desc_heads($do) $i $i] + while {1} { + set desc_heads($do) $heads + set p $allparents($do) + if {[llength $p] != 1 || ![info exists desc_heads($p)] || + $desc_heads($p) ne $oldheads} break + set do $p + } + set todo [concat $todo $p] + } +} + +# update things for a head moved to a child of its previous location +proc movedhead {id name} { + global headids idheads + + set oldid $headids($name) + set headids($name) $id + if {$idheads($oldid) eq $name} { + unset idheads($oldid) + } else { + set i [lsearch -exact $idheads($oldid) $name] + if {$i >= 0} { + set idheads($oldid) [lreplace $idheads($oldid) $i $i] + } + } + lappend idheads($id) $name +} + +proc changedrefs {} { + global desc_heads desc_tags anc_tags allcommits allids + global allchildren allparents idtags travindex + + if {![info exists allcommits]} return + catch {unset desc_heads} + catch {unset desc_tags} + catch {unset anc_tags} + catch {unset alldtags} + catch {unset tagisdesc} + foreach id $allids { + forward_pass $id $allchildren($id) + } + if {$allcommits ne "reading"} { + set travindex [llength $allids] + if {$allcommits ne "traversing"} { + set allcommits "traversing" + after idle restartatags + } } - return [list $x $y $z] } proc rereadrefs {} { - global idtags idheads idotherrefs + global idtags idheads idotherrefs mainhead set refids [concat [array names idtags] \ [array names idheads] [array names idotherrefs]] @@ -4879,17 +5721,39 @@ proc rereadrefs {} { set ref($id) [listrefs $id] } } + set oldmainhead $mainhead readrefs + changedrefs set refids [lsort -unique [concat $refids [array names idtags] \ [array names idheads] [array names idotherrefs]]] foreach id $refids { set v [listrefs $id] - if {![info exists ref($id)] || $ref($id) != $v} { + if {![info exists ref($id)] || $ref($id) != $v || + ($id eq $oldmainhead && $id ne $mainhead) || + ($id eq $mainhead && $id ne $oldmainhead)} { redrawtags $id } } } +proc listrefs {id} { + global idtags idheads idotherrefs + + set x {} + if {[info exists idtags($id)]} { + set x $idtags($id) + } + set y {} + if {[info exists idheads($id)]} { + set y $idheads($id) + } + set z {} + if {[info exists idotherrefs($id)]} { + set z $idotherrefs($id) + } + return [list $x $y $z] +} + proc showtag {tag isnew} { global ctext tagcontents tagids linknum @@ -4904,7 +5768,7 @@ proc showtag {tag isnew} { } else { set text "Tag: $tag\nId: $tagids($tag)" } - appendwithlinks $text + appendwithlinks $text {} $ctext conf -state disabled init_flist {} } @@ -4916,8 +5780,9 @@ proc doquit {} { } proc doprefs {} { - global maxwidth maxgraphpct diffopts findmergefiles - global oldprefs prefstop + global maxwidth maxgraphpct diffopts + global oldprefs prefstop showneartags + global bgcolor fgcolor ctext diffcolors set top .gitkprefs set prefstop $top @@ -4925,7 +5790,7 @@ proc doprefs {} { raise $top return } - foreach v {maxwidth maxgraphpct diffopts findmergefiles} { + foreach v {maxwidth maxgraphpct diffopts showneartags} { set oldprefs($v) [set $v] } toplevel $top @@ -4941,16 +5806,46 @@ proc doprefs {} { -font optionfont spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct grid x $top.maxpctl $top.maxpct -sticky w - checkbutton $top.findm -variable findmergefiles - label $top.findml -text "Include merges for \"Find\" in \"Files\"" \ - -font optionfont - grid $top.findm $top.findml - -sticky w + label $top.ddisp -text "Diff display options" grid $top.ddisp - -sticky w -pady 10 label $top.diffoptl -text "Options for diff program" \ -font optionfont entry $top.diffopt -width 20 -textvariable diffopts grid x $top.diffoptl $top.diffopt -sticky w + frame $top.ntag + label $top.ntag.l -text "Display nearby tags" -font optionfont + checkbutton $top.ntag.b -variable showneartags + pack $top.ntag.b $top.ntag.l -side left + grid x $top.ntag -sticky w + + label $top.cdisp -text "Colors: press to choose" + grid $top.cdisp - -sticky w -pady 10 + label $top.bg -padx 40 -relief sunk -background $bgcolor + button $top.bgbut -text "Background" -font optionfont \ + -command [list choosecolor bgcolor 0 $top.bg background setbg] + grid x $top.bgbut $top.bg -sticky w + label $top.fg -padx 40 -relief sunk -background $fgcolor + button $top.fgbut -text "Foreground" -font optionfont \ + -command [list choosecolor fgcolor 0 $top.fg foreground setfg] + grid x $top.fgbut $top.fg -sticky w + label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0] + button $top.diffoldbut -text "Diff: old lines" -font optionfont \ + -command [list choosecolor diffcolors 0 $top.diffold "diff old lines" \ + [list $ctext tag conf d0 -foreground]] + grid x $top.diffoldbut $top.diffold -sticky w + label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1] + button $top.diffnewbut -text "Diff: new lines" -font optionfont \ + -command [list choosecolor diffcolors 1 $top.diffnew "diff new lines" \ + [list $ctext tag conf d1 -foreground]] + grid x $top.diffnewbut $top.diffnew -sticky w + label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2] + button $top.hunksepbut -text "Diff: hunk header" -font optionfont \ + -command [list choosecolor diffcolors 2 $top.hunksep \ + "diff hunk header" \ + [list $ctext tag conf hunksep -foreground]] + grid x $top.hunksepbut $top.hunksep -sticky w + frame $top.buts button $top.buts.ok -text "OK" -command prefsok button $top.buts.can -text "Cancel" -command prefscan @@ -4960,11 +5855,40 @@ proc doprefs {} { grid $top.buts - - -pady 10 -sticky ew } +proc choosecolor {v vi w x cmd} { + global $v + + set c [tk_chooseColor -initialcolor [lindex [set $v] $vi] \ + -title "Gitk: choose color for $x"] + if {$c eq {}} return + $w conf -background $c + lset $v $vi $c + eval $cmd $c +} + +proc setbg {c} { + global bglist + + foreach w $bglist { + $w conf -background $c + } +} + +proc setfg {c} { + global fglist canv + + foreach w $fglist { + $w conf -foreground $c + } + allcanvs itemconf text -fill $c + $canv itemconf circle -outline $c +} + proc prefscan {} { - global maxwidth maxgraphpct diffopts findmergefiles - global oldprefs prefstop + global maxwidth maxgraphpct diffopts + global oldprefs prefstop showneartags - foreach v {maxwidth maxgraphpct diffopts findmergefiles} { + foreach v {maxwidth maxgraphpct diffopts showneartags} { set $v $oldprefs($v) } catch {destroy $prefstop} @@ -4973,13 +5897,15 @@ proc prefscan {} { proc prefsok {} { global maxwidth maxgraphpct - global oldprefs prefstop + global oldprefs prefstop showneartags catch {destroy $prefstop} unset prefstop if {$maxwidth != $oldprefs(maxwidth) || $maxgraphpct != $oldprefs(maxgraphpct)} { redisplay + } elseif {$showneartags != $oldprefs(showneartags)} { + reselectline } } @@ -5263,11 +6189,11 @@ proc tcl_encoding {enc} { # defaults... set datemode 0 set diffopts "-U 5 -p" -set wrcomcmd "git-diff-tree --stdin -p --pretty" +set wrcomcmd "git diff-tree --stdin -p --pretty" set gitencoding {} catch { - set gitencoding [exec git-repo-config --get i18n.commitencoding] + set gitencoding [exec git repo-config --get i18n.commitencoding] } if {$gitencoding == ""} { set gitencoding "utf-8" @@ -5289,8 +6215,13 @@ set uparrowlen 7 set downarrowlen 7 set mingaplen 30 set cmitmode "patch" +set wrapcomment "none" +set showneartags 1 set colors {green red blue magenta darkgrey brown orange} +set bgcolor white +set fgcolor black +set diffcolors {red "#00a000" blue} catch {source ~/.gitk} @@ -5310,7 +6241,7 @@ foreach arg $argv { # check that we can find a .git directory somewhere... set gitdir [gitdir] if {![file isdirectory $gitdir]} { - show_error . "Cannot find the git directory \"$gitdir\"." + show_error {} . "Cannot find the git directory \"$gitdir\"." exit 1 } @@ -5321,7 +6252,7 @@ if {$i >= 0} { set revtreeargs [lrange $revtreeargs 0 [expr {$i - 1}]] } elseif {$revtreeargs ne {}} { if {[catch { - set f [eval exec git-rev-parse --no-revs --no-flags $revtreeargs] + set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs] set cmdline_files [split $f "\n"] set n [llength $cmdline_files] set revtreeargs [lrange $revtreeargs 0 end-$n] @@ -5330,9 +6261,9 @@ if {$i >= 0} { # so look for "fatal:". set i [string first "fatal:" $err] if {$i > 0} { - set err [string range [expr {$i + 6}] end] + set err [string range $err [expr {$i + 6}] end] } - show_error . "Bad arguments to gitk:\n$err" + show_error {} . "Bad arguments to gitk:\n$err" exit 1 } } @@ -5340,9 +6271,11 @@ if {$i >= 0} { set history {} set historyindex 0 set fh_serial 0 -set highlight_names {} set nhl_names {} set highlight_paths {} +set searchdirn -forwards +set boldrows {} +set boldnamerows {} set optim_delay 16