X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=gitk;h=dca1741c71254175d2161c1fd58091077f6087fd;hb=478afad697e58433f34d69b5fe08511405f23506;hp=1125f68d35f1e58bfd3cdf06f3cb8ae8669415f1;hpb=c30acc77fec6b7d793af703c4700f5162db82eb7;p=git.git diff --git a/gitk b/gitk index 1125f68d3..dca1741c7 100755 --- a/gitk +++ b/gitk @@ -2,7 +2,7 @@ # Tcl ignores the next line -*- tcl -*- \ exec wish "$0" -- "$@" -# Copyright (C) 2005-2006 Paul Mackerras. All rights reserved. +# Copyright © 2005-2008 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. @@ -22,11 +22,11 @@ proc gitdir {} { # run before X event handlers, so reading from a fast source can # make the GUI completely unresponsive. proc run args { - global isonrunq runq + global isonrunq runq currunq set script $args if {[info exists isonrunq($script)]} return - if {$runq eq {}} { + if {$runq eq {} && ![info exists currunq]} { after idle dorunq } lappend runq [list {} $script] @@ -38,10 +38,10 @@ proc filerun {fd script} { } proc filereadable {fd script} { - global runq + global runq currunq fileevent $fd readable {} - if {$runq eq {}} { + if {$runq eq {} && ![info exists currunq]} { after idle dorunq } lappend runq [list $fd $script] @@ -60,17 +60,19 @@ proc nukefile {fd} { } proc dorunq {} { - global isonrunq runq + global isonrunq runq currunq set tstart [clock clicks -milliseconds] set t0 $tstart while {[llength $runq] > 0} { set fd [lindex $runq 0 0] set script [lindex $runq 0 1] + set currunq [lindex $runq 0] + set runq [lrange $runq 1 end] set repeat [eval $script] + unset currunq set t1 [clock clicks -milliseconds] set t [expr {$t1 - $t0}] - set runq [lrange $runq 1 end] if {$repeat ne {} && $repeat} { if {$fd eq {} || $repeat == 2} { # script returns 1 if it wants to be readded @@ -90,35 +92,284 @@ proc dorunq {} { } } +proc reg_instance {fd} { + global commfd leftover loginstance + + set i [incr loginstance] + set commfd($i) $fd + set leftover($i) {} + return $i +} + +proc unmerged_files {files} { + global nr_unmerged + + # find the list of unmerged files + set mlist {} + set nr_unmerged 0 + if {[catch { + set fd [open "| git ls-files -u" r] + } err]} { + show_error {} . "[mc "Couldn't get list of unmerged files:"] $err" + exit 1 + } + while {[gets $fd line] >= 0} { + set i [string first "\t" $line] + if {$i < 0} continue + set fname [string range $line [expr {$i+1}] end] + if {[lsearch -exact $mlist $fname] >= 0} continue + incr nr_unmerged + if {$files eq {} || [path_filter $files $fname]} { + lappend mlist $fname + } + } + catch {close $fd} + return $mlist +} + +proc parseviewargs {n arglist} { + global vdatemode vmergeonly vflags vdflags vrevs vfiltered vorigargs + + set vdatemode($n) 0 + set vmergeonly($n) 0 + set glflags {} + set diffargs {} + set nextisval 0 + set revargs {} + set origargs $arglist + set allknown 1 + set filtered 0 + set i -1 + foreach arg $arglist { + incr i + if {$nextisval} { + lappend glflags $arg + set nextisval 0 + continue + } + switch -glob -- $arg { + "-d" - + "--date-order" { + set vdatemode($n) 1 + # remove from origargs in case we hit an unknown option + set origargs [lreplace $origargs $i $i] + incr i -1 + } + "-[puabwcrRBMC]" - + "--no-renames" - "--full-index" - "--binary" - "--abbrev=*" - + "--find-copies-harder" - "-l*" - "--ext-diff" - "--no-ext-diff" - + "--src-prefix=*" - "--dst-prefix=*" - "--no-prefix" - + "-O*" - "--text" - "--full-diff" - "--ignore-space-at-eol" - + "--ignore-space-change" - "-U*" - "--unified=*" { + # These request or affect diff output, which we don't want. + # Some could be used to set our defaults for diff display. + lappend diffargs $arg + } + "--raw" - "--patch-with-raw" - "--patch-with-stat" - + "--name-only" - "--name-status" - "--color" - "--color-words" - + "--log-size" - "--pretty=*" - "--decorate" - "--abbrev-commit" - + "--cc" - "-z" - "--header" - "--parents" - "--boundary" - + "--no-color" - "-g" - "--walk-reflogs" - "--no-walk" - + "--timestamp" - "relative-date" - "--date=*" - "--stdin" - + "--objects" - "--objects-edge" - "--reverse" { + # These cause our parsing of git log's output to fail, or else + # they're options we want to set ourselves, so ignore them. + } + "--stat=*" - "--numstat" - "--shortstat" - "--summary" - + "--check" - "--exit-code" - "--quiet" - "--topo-order" - + "--full-history" - "--dense" - "--sparse" - + "--follow" - "--left-right" - "--encoding=*" { + # These are harmless, and some are even useful + lappend glflags $arg + } + "--diff-filter=*" - "--no-merges" - "--unpacked" - + "--max-count=*" - "--skip=*" - "--since=*" - "--after=*" - + "--until=*" - "--before=*" - "--max-age=*" - "--min-age=*" - + "--author=*" - "--committer=*" - "--grep=*" - "-[iE]" - + "--remove-empty" - "--first-parent" - "--cherry-pick" - + "-S*" - "--pickaxe-all" - "--pickaxe-regex" { + # These mean that we get a subset of the commits + set filtered 1 + lappend glflags $arg + } + "-n" { + # This appears to be the only one that has a value as a + # separate word following it + set filtered 1 + set nextisval 1 + lappend glflags $arg + } + "--not" - "--all" { + lappend revargs $arg + } + "--merge" { + set vmergeonly($n) 1 + # git rev-parse doesn't understand --merge + lappend revargs --gitk-symmetric-diff-marker MERGE_HEAD...HEAD + } + "-*" { + # Other flag arguments including - + if {[string is digit -strict [string range $arg 1 end]]} { + set filtered 1 + } else { + # a flag argument that we don't recognize; + # that means we can't optimize + set allknown 0 + } + lappend glflags $arg + } + default { + # Non-flag arguments specify commits or ranges of commits + if {[string match "*...*" $arg]} { + lappend revargs --gitk-symmetric-diff-marker + } + lappend revargs $arg + } + } + } + set vdflags($n) $diffargs + set vflags($n) $glflags + set vrevs($n) $revargs + set vfiltered($n) $filtered + set vorigargs($n) $origargs + return $allknown +} + +proc parseviewrevs {view revs} { + global vposids vnegids + + if {$revs eq {}} { + set revs HEAD + } + if {[catch {set ids [eval exec git rev-parse $revs]} err]} { + # we get stdout followed by stderr in $err + # for an unknown rev, git rev-parse echoes it and then errors out + set errlines [split $err "\n"] + set badrev {} + for {set l 0} {$l < [llength $errlines]} {incr l} { + set line [lindex $errlines $l] + if {!([string length $line] == 40 && [string is xdigit $line])} { + if {[string match "fatal:*" $line]} { + if {[string match "fatal: ambiguous argument*" $line] + && $badrev ne {}} { + if {[llength $badrev] == 1} { + set err "unknown revision $badrev" + } else { + set err "unknown revisions: [join $badrev ", "]" + } + } else { + set err [join [lrange $errlines $l end] "\n"] + } + break + } + lappend badrev $line + } + } + error_popup "[mc "Error parsing revisions:"] $err" + return {} + } + set ret {} + set pos {} + set neg {} + set sdm 0 + foreach id [split $ids "\n"] { + if {$id eq "--gitk-symmetric-diff-marker"} { + set sdm 4 + } elseif {[string match "^*" $id]} { + if {$sdm != 1} { + lappend ret $id + if {$sdm == 3} { + set sdm 0 + } + } + lappend neg [string range $id 1 end] + } else { + if {$sdm != 2} { + lappend ret $id + } else { + lset ret end [lindex $ret end]...$id + } + lappend pos $id + } + incr sdm -1 + } + set vposids($view) $pos + set vnegids($view) $neg + return $ret +} + # Start off a git log process and arrange to read its output proc start_rev_list {view} { - global startmsecs - global commfd leftover tclencoding datemode - global viewargs viewfiles commitidx viewcomplete - global showlocalchanges commitinterest mainheadid - global progressdirn progresscoords proglastnc curview - global viewactive loginstance viewinstances - global pending_select mainheadid + global startmsecs commitidx viewcomplete curview + global tclencoding + global viewargs viewargscmd viewfiles vfilelimit + global showlocalchanges + global viewactive viewinstances vmergeonly + global mainheadid viewmainheadid viewmainheadid_orig + global vcanopt vflags vrevs vorigargs set startmsecs [clock clicks -milliseconds] set commitidx($view) 0 - set viewcomplete($view) 0 - set viewactive($view) 1 + # these are set this way for the error exits + set viewcomplete($view) 1 + set viewactive($view) 0 varcinit $view + set args $viewargs($view) + if {$viewargscmd($view) ne {}} { + if {[catch { + set str [exec sh -c $viewargscmd($view)] + } err]} { + error_popup "[mc "Error executing --argscmd command:"] $err" + return 0 + } + set args [concat $args [split $str "\n"]] + } + set vcanopt($view) [parseviewargs $view $args] + + set files $viewfiles($view) + if {$vmergeonly($view)} { + set files [unmerged_files $files] + if {$files eq {}} { + global nr_unmerged + if {$nr_unmerged == 0} { + error_popup [mc "No files selected: --merge specified but\ + no files are unmerged."] + } else { + error_popup [mc "No files selected: --merge specified but\ + no unmerged files are within file limit."] + } + return 0 + } + } + set vfilelimit($view) $files + + if {$vcanopt($view)} { + set revs [parseviewrevs $view $vrevs($view)] + if {$revs eq {}} { + return 0 + } + set args [concat $vflags($view) $revs] + } else { + set args $vorigargs($view) + } + if {[catch { set fd [open [concat | git log --no-color -z --pretty=raw --parents \ - --boundary $viewargs($view) "--" $viewfiles($view)] r] + --boundary $args "--" $files] r] } err]} { error_popup "[mc "Error executing git log:"] $err" - exit 1 + return 0 } - set i [incr loginstance] + set i [reg_instance $fd] set viewinstances($view) [list $i] - set commfd($i) $fd - set leftover($i) {} - if {$showlocalchanges} { - lappend commitinterest($mainheadid) {dodiffindex} + set viewmainheadid($view) $mainheadid + set viewmainheadid_orig($view) $mainheadid + if {$files ne {} && $mainheadid ne {}} { + get_viewmainhead $view + } + if {$showlocalchanges && $viewmainheadid($view) ne {}} { + interestedin $viewmainheadid($view) dodiffindex } fconfigure $fd -blocking 0 -translation lf -eofchar {} if {$tclencoding != {}} { @@ -126,72 +377,142 @@ proc start_rev_list {view} { } filerun $fd [list getcommitlines $fd $i $view 0] nowbusy $view [mc "Reading"] - if {$view == $curview} { - set progressdirn 1 - set progresscoords {0 0} - set proglastnc 0 - set pending_select $mainheadid + set viewcomplete($view) 0 + set viewactive($view) 1 + return 1 +} + +proc stop_instance {inst} { + global commfd leftover + + set fd $commfd($inst) + catch { + set pid [pid $fd] + + if {$::tcl_platform(platform) eq {windows}} { + exec kill -f $pid + } else { + exec kill $pid + } + } + catch {close $fd} + nukefile $fd + unset commfd($inst) + unset leftover($inst) +} + +proc stop_backends {} { + global commfd + + foreach inst [array names commfd] { + stop_instance $inst } } proc stop_rev_list {view} { - global commfd viewinstances leftover + global viewinstances foreach inst $viewinstances($view) { - set fd $commfd($inst) - catch { - set pid [pid $fd] - exec kill $pid - } - catch {close $fd} - nukefile $fd - unset commfd($inst) - unset leftover($inst) + stop_instance $inst } set viewinstances($view) {} } -proc getcommits {} { - global canv curview need_redisplay +proc reset_pending_select {selid} { + global pending_select mainheadid selectheadid + + if {$selid ne {}} { + set pending_select $selid + } elseif {$selectheadid ne {}} { + set pending_select $selectheadid + } else { + set pending_select $mainheadid + } +} + +proc getcommits {selid} { + global canv curview need_redisplay viewactive initlayout - start_rev_list $curview - show_status [mc "Reading commits..."] - set need_redisplay 1 + if {[start_rev_list $curview]} { + reset_pending_select $selid + show_status [mc "Reading commits..."] + set need_redisplay 1 + } else { + show_status [mc "No commits selected"] + } } proc updatecommits {} { - global curview viewargs viewfiles viewinstances - global viewactive viewcomplete loginstance tclencoding mainheadid - global startmsecs commfd showneartags showlocalchanges leftover - global mainheadid pending_select - - set oldmainid $mainheadid + global curview vcanopt vorigargs vfilelimit viewinstances + global viewactive viewcomplete tclencoding + global startmsecs showneartags showlocalchanges + global mainheadid viewmainheadid viewmainheadid_orig pending_select + global isworktree + global varcid vposids vnegids vflags vrevs + + set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}] rereadrefs - if {$showlocalchanges} { - if {$mainheadid ne $oldmainid} { + set view $curview + if {$mainheadid ne $viewmainheadid_orig($view)} { + if {$showlocalchanges} { dohidelocalchanges } - if {[commitinview $mainheadid $curview]} { - dodiffindex + set viewmainheadid($view) $mainheadid + set viewmainheadid_orig($view) $mainheadid + if {$vfilelimit($view) ne {}} { + get_viewmainhead $view } } - set view $curview + if {$showlocalchanges} { + doshowlocalchanges + } + if {$vcanopt($view)} { + set oldpos $vposids($view) + set oldneg $vnegids($view) + set revs [parseviewrevs $view $vrevs($view)] + if {$revs eq {}} { + return + } + # note: getting the delta when negative refs change is hard, + # and could require multiple git log invocations, so in that + # case we ask git log for all the commits (not just the delta) + if {$oldneg eq $vnegids($view)} { + set newrevs {} + set npos 0 + # take out positive refs that we asked for before or + # that we have already seen + foreach rev $revs { + if {[string length $rev] == 40} { + if {[lsearch -exact $oldpos $rev] < 0 + && ![info exists varcid($view,$rev)]} { + lappend newrevs $rev + incr npos + } + } else { + lappend $newrevs $rev + } + } + if {$npos == 0} return + set revs $newrevs + set vposids($view) [lsort -unique [concat $oldpos $vposids($view)]] + } + set args [concat $vflags($view) $revs --not $oldpos] + } else { + set args $vorigargs($view) + } if {[catch { set fd [open [concat | git log --no-color -z --pretty=raw --parents \ - --boundary $viewargs($view) --not [seeds $view] \ - "--" $viewfiles($view)] r] + --boundary $args "--" $vfilelimit($view)] r] } err]} { - error_popup "Error executing git log: $err" - exit 1 + error_popup "[mc "Error executing git log:"] $err" + return } if {$viewactive($view) == 0} { set startmsecs [clock clicks -milliseconds] } - set i [incr loginstance] + set i [reg_instance $fd] lappend viewinstances($view) $i - set commfd($i) $fd - set leftover($i) {} fconfigure $fd -blocking 0 -translation lf -eofchar {} if {$tclencoding != {}} { fconfigure $fd -encoding $tclencoding @@ -199,8 +520,8 @@ proc updatecommits {} { filerun $fd [list getcommitlines $fd $i $view 1] incr viewactive($view) set viewcomplete($view) 0 - set pending_select $mainheadid - nowbusy $view "Reading" + reset_pending_select {} + nowbusy $view [mc "Reading"] if {$showneartags} { getallcommits } @@ -209,15 +530,18 @@ proc updatecommits {} { proc reloadcommits {} { global curview viewcomplete selectedline currentid thickerline global showneartags treediffs commitinterest cached_commitrow - global progresscoords targetid + global targetid + + set selid {} + if {$selectedline ne {}} { + set selid $currentid + } if {!$viewcomplete($curview)} { stop_rev_list $curview - set progresscoords {0 0} - adjustprogress } resetvarcs $curview - catch {unset selectedline} + set selectedline {} catch {unset currentid} catch {unset thickerline} catch {unset treediffs} @@ -231,7 +555,7 @@ proc reloadcommits {} { catch {unset cached_commitrow} catch {unset targetid} setcanvscroll - getcommits + getcommits $selid return 0 } @@ -304,13 +628,13 @@ proc seeds {v} { } proc newvarc {view id} { - global varcid varctok parents children datemode + global varcid varctok parents children vdatemode global vupptr vdownptr vleftptr vbackptr varcrow varcix varcstart global commitdata commitinfo vseedcount varccommits vlastins set a [llength $varctok($view)] set vid $view,$id - if {[llength $children($vid)] == 0 || $datemode} { + if {[llength $children($vid)] == 0 || $vdatemode($view)} { if {![info exists commitinfo($id)]} { parsecommit $id $commitdata($id) 1 } @@ -377,16 +701,17 @@ proc newvarc {view id} { } proc splitvarc {p v} { - global varcid varcstart varccommits varctok + global varcid varcstart varccommits varctok vtokmod global vupptr vdownptr vleftptr vbackptr varcix varcrow vlastins set oa $varcid($v,$p) + set otok [lindex $varctok($v) $oa] set ac $varccommits($v,$oa) set i [lsearch -exact $varccommits($v,$oa) $p] if {$i <= 0} return set na [llength $varctok($v)] # "%" sorts before "0"... - set tok "[lindex $varctok($v) $oa]%[strrep $i]" + set tok "$otok%[strrep $i]" lappend varctok($v) $tok lappend varcrow($v) {} lappend varcix($v) {} @@ -406,11 +731,14 @@ proc splitvarc {p v} { for {set b [lindex $vdownptr($v) $na]} {$b != 0} {set b [lindex $vleftptr($v) $b]} { lset vupptr($v) $b $na } + if {[string compare $otok $vtokmod($v)] <= 0} { + modify_arc $v $oa + } } proc renumbervarc {a v} { global parents children varctok varcstart varccommits - global vupptr vdownptr vleftptr vbackptr vlastins varcid vtokmod datemode + global vupptr vdownptr vleftptr vbackptr vlastins varcid vtokmod vdatemode set t1 [clock clicks -milliseconds] set todo {} @@ -446,7 +774,7 @@ proc renumbervarc {a v} { $children($v,$id)] } set oldtok [lindex $varctok($v) $a] - if {!$datemode} { + if {!$vdatemode($v)} { set tok {} } else { set tok $oldtok @@ -649,7 +977,7 @@ proc removefakerow {id} { modify_arc $v $a $i if {[info exist currentid] && $id eq $currentid} { unset currentid - unset selectedline + set selectedline {} } if {[info exists targetid] && $targetid eq $id} { set targetid $p @@ -912,7 +1240,7 @@ proc commitonrow {row} { proc closevarcs {v} { global varctok varccommits varcid parents children - global cmitlisted commitidx commitinterest vtokmod + global cmitlisted commitidx vtokmod set missing_parents 0 set scripts {} @@ -937,12 +1265,7 @@ proc closevarcs {v} { } lappend varccommits($v,$b) $p incr commitidx($v) - if {[info exists commitinterest($p)]} { - foreach script $commitinterest($p) { - lappend scripts [string map [list "%I" $p] $script] - } - unset commitinterest($id) - } + set scripts [check_interest $p $scripts] } } if {$missing_parents > 0} { @@ -978,12 +1301,45 @@ proc rewrite_commit {v id rwid} { } } +# Mechanism for registering a command to be executed when we come +# across a particular commit. To handle the case when only the +# prefix of the commit is known, the commitinterest array is now +# indexed by the first 4 characters of the ID. Each element is a +# list of id, cmd pairs. +proc interestedin {id cmd} { + global commitinterest + + lappend commitinterest([string range $id 0 3]) $id $cmd +} + +proc check_interest {id scripts} { + global commitinterest + + set prefix [string range $id 0 3] + if {[info exists commitinterest($prefix)]} { + set newlist {} + foreach {i script} $commitinterest($prefix) { + if {[string match "$i*" $id]} { + lappend scripts [string map [list "%I" $id "%P" $i] $script] + } else { + lappend newlist $i $script + } + } + if {$newlist ne {}} { + set commitinterest($prefix) $newlist + } else { + unset commitinterest($prefix) + } + } + return $scripts +} + proc getcommitlines {fd inst view updating} { - global cmitlisted commitinterest leftover - global commitidx commitdata datemode + global cmitlisted leftover + global commitidx commitdata vdatemode global parents children curview hlview global idpending ordertok - global varccommits varcid varctok vtokmod viewfiles + global varccommits varcid varctok vtokmod vfilelimit set stuff [read $fd 500000] # git log doesn't terminate the last commit with a null... @@ -994,7 +1350,7 @@ proc getcommitlines {fd inst view updating} { if {![eof $fd]} { return 1 } - global commfd viewcomplete viewactive viewname progresscoords + global commfd viewcomplete viewactive viewname global viewinstances unset commfd($inst) set i [lsearch -exact $viewinstances($view) $inst] @@ -1027,8 +1383,6 @@ proc getcommitlines {fd inst view updating} { # appeared in the list closevarcs $view notbusy $view - set progresscoords {0 0} - adjustprogress } if {$view == $curview} { run chewcommits @@ -1086,7 +1440,7 @@ proc getcommitlines {fd inst view updating} { set vid $view,$id if {!$listed && $updating && ![info exists varcid($vid)] && - $viewfiles($view) ne {}} { + $vfilelimit($view) ne {}} { # git log doesn't rewrite parents for unlisted commits # when doing path limiting, so work around that here # by working out the rewritten parent with git rev-list @@ -1094,7 +1448,7 @@ proc getcommitlines {fd inst view updating} { # parent as a substitute parent for $id's children. if {![catch { set rwid [exec git rev-list --first-parent --max-count=1 \ - $id -- $viewfiles($view)] + $id -- $vfilelimit($view)] }]} { if {$rwid ne {} && [info exists varcid($view,$rwid)]} { # use $rwid in place of $id @@ -1122,7 +1476,7 @@ proc getcommitlines {fd inst view updating} { } elseif {$a == 0 && [llength $children($vid)] == 1} { set k [lindex $children($vid) 0] if {[llength $parents($view,$k)] == 1 && - (!$datemode || + (!$vdatemode($view) || $varcid($view,$k) == [llength $varctok($view)] - 1)} { set a $varcid($view,$k) } @@ -1157,12 +1511,7 @@ proc getcommitlines {fd inst view updating} { incr i } - if {[info exists commitinterest($id)]} { - foreach script $commitinterest($id) { - lappend scripts [string map [list "%I" $id] $script] - } - unset commitinterest($id) - } + set scripts [check_interest $id $scripts] set gotsome 1 } if {$gotsome} { @@ -1179,33 +1528,6 @@ proc getcommitlines {fd inst view updating} { foreach s $scripts { eval $s } - if {$view == $curview} { - # update progress bar - global progressdirn progresscoords proglastnc - set inc [expr {($commitidx($view) - $proglastnc) * 0.0002}] - set proglastnc $commitidx($view) - set l [lindex $progresscoords 0] - set r [lindex $progresscoords 1] - if {$progressdirn} { - set r [expr {$r + $inc}] - if {$r >= 1.0} { - set r 1.0 - set progressdirn 0 - } - if {$r > 0.2} { - set l [expr {$r - 0.2}] - } - } else { - set l [expr {$l - $inc}] - if {$l <= 0.0} { - set l 0.0 - set progressdirn 1 - } - set r [expr {$l + 0.2}] - } - set progresscoords [list $l $r] - adjustprogress - } } return 2 } @@ -1218,11 +1540,17 @@ proc chewcommits {} { if {$viewcomplete($curview)} { global commitidx varctok global numcommits startmsecs - global mainheadid nullid if {[info exists pending_select]} { - set row [first_real_row] - selectline $row 1 + update + reset_pending_select {} + + if {[commitinview $pending_select $curview]} { + selectline [rowofcommit $pending_select] 1 + } else { + set row [first_real_row] + selectline $row 1 + } } if {$commitidx($curview) > 0} { #set ms [expr {[clock clicks -milliseconds] - $startmsecs}] @@ -1236,9 +1564,27 @@ proc chewcommits {} { return 0 } +proc do_readcommit {id} { + global tclencoding + + # Invoke git-log to handle automatic encoding conversion + set fd [open [concat | git log --no-color --pretty=raw -1 $id] r] + # Read the results using i18n.logoutputencoding + fconfigure $fd -translation lf -eofchar {} + if {$tclencoding != {}} { + fconfigure $fd -encoding $tclencoding + } + set contents [read $fd] + close $fd + # Remove the heading line + regsub {^commit [0-9a-f]+\n} $contents {} contents + + return $contents +} + proc readcommit {id} { - if {[catch {set contents [exec git cat-file commit $id]}]} return - parsecommit $id $contents 0 + if {[catch {set contents [do_readcommit $id]}]} return + parsecommit $id $contents 1 } proc parsecommit {id contents listed} { @@ -1259,13 +1605,14 @@ proc parsecommit {id contents listed} { set header [string range $contents 0 [expr {$hdrend - 1}]] set comment [string range $contents [expr {$hdrend + 2}] end] foreach line [split $header "\n"] { + set line [split $line " "] set tag [lindex $line 0] if {$tag == "author"} { set audate [lindex $line end-1] - set auname [lrange $line 1 end-2] + set auname [join [lrange $line 1 end-2] " "] } elseif {$tag == "committer"} { set comdate [lindex $line end-1] - set comname [lrange $line 1 end-2] + set comname [join [lrange $line 1 end-2] " "] } } set headline {} @@ -1312,9 +1659,23 @@ proc getcommit {id} { return 1 } +# Expand an abbreviated commit ID to a list of full 40-char IDs that match +# and are present in the current view. +# This is fairly slow... +proc longid {prefix} { + global varcid curview + + set ids {} + foreach match [array names varcid "$curview,$prefix*"] { + lappend ids [lindex [split $match ","] 1] + } + return $ids +} + proc readrefs {} { global tagids idtags headids idheads tagobjid global otherrefids idotherrefs mainhead mainheadid + global selecthead selectheadid foreach v {tagids idtags headids idheads otherrefids idotherrefs} { catch {unset $v} @@ -1355,12 +1716,16 @@ proc readrefs {} { set mainhead {} set mainheadid {} catch { + set mainheadid [exec git rev-parse HEAD] set thehead [exec git symbolic-ref HEAD] if {[string match "refs/heads/*" $thehead]} { set mainhead [string range $thehead 11 end] - if {[info exists headids($mainhead)]} { - set mainheadid $headids($mainhead) - } + } + } + set selectheadid {} + if {$selecthead ne {}} { + catch { + set selectheadid [exec git rev-parse --verify $selecthead] } } } @@ -1402,6 +1767,24 @@ proc removehead {id name} { unset headids($name) } +proc make_transient {window origin} { + global have_tk85 + + # In MacOS Tk 8.4 transient appears to work by setting + # overrideredirect, which is utterly useless, since the + # windows get no border, and are not even kept above + # the parent. + if {!$have_tk85 && [tk windowingsystem] eq {aqua}} return + + wm transient $window $origin + + # Windows fails to place transient windows normally, so + # schedule a callback to center them on the parent. + if {[tk windowingsystem] eq {win32}} { + after idle [list tk::PlaceWindow $window widget $origin] + } +} + 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 @@ -1409,22 +1792,24 @@ proc show_error {w top msg} { pack $w.ok -side bottom -fill x bind $top "grab $top; focus $top" bind $top "destroy $top" + bind $top "destroy $top" + bind $top "destroy $top" tkwait window $top } -proc error_popup msg { +proc error_popup {msg {owner .}} { set w .error toplevel $w - wm transient $w . + make_transient $w $owner show_error $w $w $msg } -proc confirm_popup msg { +proc confirm_popup {msg {owner .}} { global confirm_ok set confirm_ok 0 set w .confirm toplevel $w - wm transient $w . + make_transient $w $owner message $w.m -text $msg -justify center -aspect 400 pack $w.m -side top -fill x -padx 20 -pady 20 button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w" @@ -1432,6 +1817,9 @@ proc confirm_popup msg { button $w.cancel -text [mc Cancel] -command "destroy $w" pack $w.cancel -side right -fill x bind $w "grab $w; focus $w" + bind $w "set confirm_ok 1; destroy $w" + bind $w "set confirm_ok 1; destroy $w" + bind $w "destroy $w" tkwait window $w return $confirm_ok } @@ -1442,13 +1830,69 @@ proc setoptions {} { option add *Button.font uifont startupFile option add *Checkbutton.font uifont startupFile option add *Radiobutton.font uifont startupFile - option add *Menu.font uifont startupFile + if {[tk windowingsystem] ne "aqua"} { + option add *Menu.font uifont startupFile + } option add *Menubutton.font uifont startupFile option add *Label.font uifont startupFile option add *Message.font uifont startupFile option add *Entry.font uifont startupFile } +# Make a menu and submenus. +# m is the window name for the menu, items is the list of menu items to add. +# Each item is a list {mc label type description options...} +# mc is ignored; it's so we can put mc there to alert xgettext +# label is the string that appears in the menu +# type is cascade, command or radiobutton (should add checkbutton) +# description depends on type; it's the sublist for cascade, the +# command to invoke for command, or {variable value} for radiobutton +proc makemenu {m items} { + menu $m + if {[tk windowingsystem] eq {aqua}} { + set Meta1 Cmd + } else { + set Meta1 Ctrl + } + foreach i $items { + set name [mc [lindex $i 1]] + set type [lindex $i 2] + set thing [lindex $i 3] + set params [list $type] + if {$name ne {}} { + set u [string first "&" [string map {&& x} $name]] + lappend params -label [string map {&& & & {}} $name] + if {$u >= 0} { + lappend params -underline $u + } + } + switch -- $type { + "cascade" { + set submenu [string tolower [string map {& ""} [lindex $i 1]]] + lappend params -menu $m.$submenu + } + "command" { + lappend params -command $thing + } + "radiobutton" { + lappend params -variable [lindex $thing 0] \ + -value [lindex $thing 1] + } + } + set tail [lrange $i 4 end] + regsub -all {\yMeta1\y} $tail $Meta1 tail + eval $m add $params $tail + if {$type eq "cascade"} { + makemenu $m.$submenu $thing + } + } +} + +# translate string and remove ampersands +proc mca {str} { + return [string map {&& & & {}} [mc $str]] +} + proc makewindow {} { global canv canv2 canv3 linespc charspc ctext cflist cscroll global tabstop @@ -1463,36 +1907,57 @@ proc makewindow {} { global bgcolor fgcolor bglist fglist diffcolors selectbgcolor global headctxmenu progresscanv progressitem progresscoords statusw global fprogitem fprogcoord lastprogupdate progupdatepending - global rprogitem rprogcoord + global rprogitem rprogcoord rownumsel numcommits global have_tk85 - menu .bar - .bar add cascade -label [mc "File"] -menu .bar.file - menu .bar.file - .bar.file add command -label [mc "Update"] -command updatecommits - .bar.file add command -label [mc "Reload"] -command reloadcommits - .bar.file add command -label [mc "Reread references"] -command rereadrefs - .bar.file add command -label [mc "List references"] -command showrefs - .bar.file add command -label [mc "Quit"] -command doquit - menu .bar.edit - .bar add cascade -label [mc "Edit"] -menu .bar.edit - .bar.edit add command -label [mc "Preferences"] -command doprefs - - menu .bar.view - .bar add cascade -label [mc "View"] -menu .bar.view - .bar.view add command -label [mc "New view..."] -command {newview 0} - .bar.view add command -label [mc "Edit view..."] -command editview \ - -state disabled - .bar.view add command -label [mc "Delete view"] -command delview -state disabled - .bar.view add separator - .bar.view add radiobutton -label [mc "All files"] -command {showview 0} \ - -variable selectedview -value 0 - - menu .bar.help - .bar add cascade -label [mc "Help"] -menu .bar.help - .bar.help add command -label [mc "About gitk"] -command about - .bar.help add command -label [mc "Key bindings"] -command keys - .bar.help configure + # The "mc" arguments here are purely so that xgettext + # sees the following string as needing to be translated + set file { + mc "File" cascade { + {mc "Update" command updatecommits -accelerator F5} + {mc "Reload" command reloadcommits -accelerator Meta1-F5} + {mc "Reread references" command rereadrefs} + {mc "List references" command showrefs -accelerator F2} + {xx "" separator} + {mc "Start git gui" command {exec git gui &}} + {xx "" separator} + {mc "Quit" command doquit -accelerator Meta1-Q} + }} + set edit { + mc "Edit" cascade { + {mc "Preferences" command doprefs} + }} + set view { + mc "View" cascade { + {mc "New view..." command {newview 0} -accelerator Shift-F4} + {mc "Edit view..." command editview -state disabled -accelerator F4} + {mc "Delete view" command delview -state disabled} + {xx "" separator} + {mc "All files" radiobutton {selectedview 0} -command {showview 0}} + }} + if {[tk windowingsystem] ne "aqua"} { + set help { + mc "Help" cascade { + {mc "About gitk" command about} + {mc "Key bindings" command keys} + }} + set bar [list $file $edit $view $help] + } else { + proc ::tk::mac::ShowPreferences {} {doprefs} + proc ::tk::mac::Quit {} {doquit} + lset file end [lreplace [lindex $file end] end-1 end] + set apple { + xx "Apple" cascade { + {mc "About gitk" command about} + {xx "" separator} + }} + set help { + mc "Help" cascade { + {mc "Key bindings" command keys} + }} + set bar [list $apple $file $view $help] + } + makemenu .bar $bar . configure -menu .bar # the gui has upper and lower half, parts of a paned window. @@ -1579,6 +2044,18 @@ proc makewindow {} { -state disabled -width 26 pack .tf.bar.rightbut -side left -fill y + label .tf.bar.rowlabel -text [mc "Row"] + set rownumsel {} + label .tf.bar.rownum -width 7 -font textfont -textvariable rownumsel \ + -relief sunken -anchor e + label .tf.bar.rowlabel2 -text "/" + label .tf.bar.numcommits -width 7 -font textfont -textvariable numcommits \ + -relief sunken -anchor e + pack .tf.bar.rowlabel .tf.bar.rownum .tf.bar.rowlabel2 .tf.bar.numcommits \ + -side left + global selectedline + trace add variable selectedline write selectedline_change + # Status label and progress bar set statusw .tf.bar.status label $statusw -width 15 -relief sunken @@ -1650,6 +2127,7 @@ proc makewindow {} { } frame .bleft.top frame .bleft.mid + frame .bleft.bottom button .bleft.top.search -text [mc "Search"] -command dosearch pack .bleft.top.search -side left -padx 5 @@ -1677,18 +2155,25 @@ proc makewindow {} { checkbutton .bleft.mid.ignspace -text [mc "Ignore space change"] \ -command changeignorespace -variable ignorespace pack .bleft.mid.ignspace -side left -padx 5 - set ctext .bleft.ctext + set ctext .bleft.bottom.ctext text $ctext -background $bgcolor -foreground $fgcolor \ -state disabled -font textfont \ - -yscrollcommand scrolltext -wrap none + -yscrollcommand scrolltext -wrap none \ + -xscrollcommand ".bleft.bottom.sbhorizontal set" if {$have_tk85} { $ctext conf -tabstyle wordprocessor } - scrollbar .bleft.sb -command "$ctext yview" + scrollbar .bleft.bottom.sb -command "$ctext yview" + scrollbar .bleft.bottom.sbhorizontal -command "$ctext xview" -orient h \ + -width 10 pack .bleft.top -side top -fill x pack .bleft.mid -side top -fill x - pack .bleft.sb -side right -fill y - pack $ctext -side left -fill both -expand 1 + grid $ctext .bleft.bottom.sb -sticky nsew + grid .bleft.bottom.sbhorizontal -sticky ew + grid columnconfigure .bleft.bottom 0 -weight 1 + grid rowconfigure .bleft.bottom 0 -weight 1 + grid rowconfigure .bleft.bottom 1 -weight 0 + pack .bleft.bottom -side top -fill both -expand 1 lappend bglist $ctext lappend fglist $ctext @@ -1696,7 +2181,7 @@ proc makewindow {} { $ctext tag conf filesep -font textfontbold -back "#aaaaaa" $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 dresult -fore [lindex $diffcolors 1] $ctext tag conf m0 -fore red $ctext tag conf m1 -fore blue $ctext tag conf m2 -fore green @@ -1753,15 +2238,29 @@ proc makewindow {} { .pwbottom add .bright .ctop add .pwbottom - # restore window position if known + # restore window width & height if known if {[info exists geometry(main)]} { - wm geometry . "$geometry(main)" + if {[scan $geometry(main) "%dx%d" w h] >= 2} { + if {$w > [winfo screenwidth .]} { + set w [winfo screenwidth .] + } + if {$h > [winfo screenheight .]} { + set h [winfo screenheight .] + } + wm geometry . "${w}x$h" + } + } + + if {[info exists geometry(state)] && $geometry(state) eq "zoomed"} { + wm state . $geometry(state) } if {[tk windowingsystem] eq {aqua}} { set M1B M1 + set ::BM "3" } else { set M1B Control + set ::BM "2" } bind .pwbottom {resizecdetpanes %W %w} @@ -1779,10 +2278,14 @@ proc makewindow {} { set delta [expr {- (%D)}] allcanvs yview scroll $delta units } + bindall { + set delta [expr {- (%D)}] + $canv xview scroll $delta units + } } } - bindall <2> "canvscan mark %W %x %y" - bindall "canvscan dragto %W %x %y" + bindall <$::BM> "canvscan mark %W %x %y" + bindall "canvscan dragto %W %x %y" bindkey selfirstline bindkey sellastline bind . "selnextline -1" @@ -1810,14 +2313,20 @@ proc makewindow {} { bindkey k "selnextline 1" bindkey j "goback" bindkey l "goforw" - bindkey b "$ctext yview scroll -1 pages" + bindkey b prevfile bindkey d "$ctext yview scroll 18 units" bindkey u "$ctext yview scroll -18 units" - bindkey / {dofind 1 1} + bindkey / {focus $fstring} + bindkey {focus $fstring} bindkey {dofind 1 1} bindkey ? {dofind -1 1} bindkey f nextfile - bindkey updatecommits + bind . updatecommits + bind . <$M1B-F5> reloadcommits + bind . showrefs + bind . {newview 0} + catch { bind . {newview 0} } + bind . edit_or_newview bind . <$M1B-q> doquit bind . <$M1B-f> {dofind 1 1} bind . <$M1B-g> {dofind 1 0} @@ -1829,59 +2338,71 @@ proc makewindow {} { bind . <$M1B-minus> {incrfont -1} bind . <$M1B-KP_Subtract> {incrfont -1} wm protocol . WM_DELETE_WINDOW doquit + bind . {stop_backends} bind . "click %W" bind $fstring {dofind 1 1} - bind $sha1entry gotocommit + bind $sha1entry {gotocommit; break} bind $sha1entry <> clearsha1 bind $cflist <1> {sel_flist %W %x %y; break} bind $cflist {sel_flist %W %x %y; break} bind $cflist {treeclick %W %x %y} - bind $cflist {pop_flist_menu %W %X %Y %x %y} + global ctxbut + bind $cflist $ctxbut {pop_flist_menu %W %X %Y %x %y} + bind $ctext $ctxbut {pop_diff_menu %W %X %Y %x %y} set maincursor [. cget -cursor] set textcursor [$ctext cget -cursor] set curtextcursor $textcursor set rowctxmenu .rowctxmenu - menu $rowctxmenu -tearoff 0 - $rowctxmenu add command -label [mc "Diff this -> selected"] \ - -command {diffvssel 0} - $rowctxmenu add command -label [mc "Diff selected -> this"] \ - -command {diffvssel 1} - $rowctxmenu add command -label [mc "Make patch"] -command mkpatch - $rowctxmenu add command -label [mc "Create tag"] -command mktag - $rowctxmenu add command -label [mc "Write commit to file"] -command writecommit - $rowctxmenu add command -label [mc "Create new branch"] -command mkbranch - $rowctxmenu add command -label [mc "Cherry-pick this commit"] \ - -command cherrypick - $rowctxmenu add command -label [mc "Reset HEAD branch to here"] \ - -command resethead + makemenu $rowctxmenu { + {mc "Diff this -> selected" command {diffvssel 0}} + {mc "Diff selected -> this" command {diffvssel 1}} + {mc "Make patch" command mkpatch} + {mc "Create tag" command mktag} + {mc "Write commit to file" command writecommit} + {mc "Create new branch" command mkbranch} + {mc "Cherry-pick this commit" command cherrypick} + {mc "Reset HEAD branch to here" command resethead} + {mc "Mark this commit" command markhere} + {mc "Return to mark" command gotomark} + {mc "Find descendant of this and mark" command find_common_desc} + {mc "Compare with marked commit" command compare_commits} + } + $rowctxmenu configure -tearoff 0 set fakerowmenu .fakerowmenu - menu $fakerowmenu -tearoff 0 - $fakerowmenu add command -label [mc "Diff this -> selected"] \ - -command {diffvssel 0} - $fakerowmenu add command -label [mc "Diff selected -> this"] \ - -command {diffvssel 1} - $fakerowmenu add command -label [mc "Make patch"] -command mkpatch -# $fakerowmenu add command -label [mc "Commit"] -command {mkcommit 0} -# $fakerowmenu add command -label [mc "Commit all"] -command {mkcommit 1} -# $fakerowmenu add command -label [mc "Revert local changes"] -command revertlocal + makemenu $fakerowmenu { + {mc "Diff this -> selected" command {diffvssel 0}} + {mc "Diff selected -> this" command {diffvssel 1}} + {mc "Make patch" command mkpatch} + } + $fakerowmenu configure -tearoff 0 set headctxmenu .headctxmenu - menu $headctxmenu -tearoff 0 - $headctxmenu add command -label [mc "Check out this branch"] \ - -command cobranch - $headctxmenu add command -label [mc "Remove this branch"] \ - -command rmbranch + makemenu $headctxmenu { + {mc "Check out this branch" command cobranch} + {mc "Remove this branch" command rmbranch} + } + $headctxmenu configure -tearoff 0 global flist_menu set flist_menu .flistctxmenu - menu $flist_menu -tearoff 0 - $flist_menu add command -label [mc "Highlight this too"] \ - -command {flist_hl 0} - $flist_menu add command -label [mc "Highlight this only"] \ - -command {flist_hl 1} + makemenu $flist_menu { + {mc "Highlight this too" command {flist_hl 0}} + {mc "Highlight this only" command {flist_hl 1}} + {mc "External diff" command {external_diff}} + {mc "Blame parent commit" command {external_blame 1}} + } + $flist_menu configure -tearoff 0 + + global diff_menu + set diff_menu .diffctxmenu + makemenu $diff_menu { + {mc "Show origin of this line" command show_line_source} + {mc "Run git gui blame on this line" command {external_blame_diff}} + } + $diff_menu configure -tearoff 0 } # Windows sends all mouse wheel events to the current focused window, not @@ -1902,6 +2423,17 @@ proc windows_mousewheel_redirector {W X Y D} { } } +# Update row number label when selectedline changes +proc selectedline_change {n1 n2 op} { + global selectedline rownumsel + + if {$selectedline eq {}} { + set rownumsel {} + } else { + set rownumsel [expr {$selectedline + 1}] + } +} + # mouse-2 makes all windows scan vertically, but only the one # the cursor is in scans horizontally proc canvscan {op w x y} { @@ -1983,14 +2515,18 @@ proc savestuff {w} { global canv canv2 canv3 mainfont textfont uifont tabstop global stuffsaved findmergefiles maxgraphpct global maxwidth showneartags showlocalchanges - global viewname viewfiles viewargs viewperm nextviewnum + global viewname viewfiles viewargs viewargscmd viewperm nextviewnum global cmitmode wrapcomment datetimeformat limitdiffs global colors bgcolor fgcolor diffcolors diffcontext selectbgcolor + global autoselect extdifftool perfile_attrs markbgcolor if {$stuffsaved} return if {![winfo viewable .]} return catch { set f [open "~/.gitk-new" w] + if {$::tcl_platform(platform) eq {windows}} { + file attributes "~/.gitk-new" -hidden true + } puts $f [list set mainfont $mainfont] puts $f [list set textfont $textfont] puts $f [list set uifont $uifont] @@ -2000,6 +2536,7 @@ proc savestuff {w} { puts $f [list set maxwidth $maxwidth] puts $f [list set cmitmode $cmitmode] puts $f [list set wrapcomment $wrapcomment] + puts $f [list set autoselect $autoselect] puts $f [list set showneartags $showneartags] puts $f [list set showlocalchanges $showlocalchanges] puts $f [list set datetimeformat $datetimeformat] @@ -2008,10 +2545,14 @@ proc savestuff {w} { puts $f [list set fgcolor $fgcolor] puts $f [list set colors $colors] puts $f [list set diffcolors $diffcolors] + puts $f [list set markbgcolor $markbgcolor] puts $f [list set diffcontext $diffcontext] puts $f [list set selectbgcolor $selectbgcolor] + puts $f [list set extdifftool $extdifftool] + puts $f [list set perfile_attrs $perfile_attrs] puts $f "set geometry(main) [wm geometry .]" + puts $f "set geometry(state) [wm state .]" puts $f "set geometry(topwidth) [winfo width .tf]" puts $f "set geometry(topheight) [winfo height .tf]" puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\"" @@ -2022,7 +2563,7 @@ proc savestuff {w} { puts -nonewline $f "set permviews {" for {set v 0} {$v < $nextviewnum} {incr v} { if {$viewperm($v)} { - puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v)]}" + puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v) $viewargscmd($v)]}" } } puts $f "}" @@ -2107,10 +2648,11 @@ proc about {} { } toplevel $w wm title $w [mc "About gitk"] + make_transient $w . message $w.m -text [mc " Gitk - a commit viewer for git -Copyright © 2005-2006 Paul Mackerras +Copyright © 2005-2008 Paul Mackerras Use and redistribute under the terms of the GNU General Public License"] \ -justify center -aspect 400 -border 2 -bg white -relief groove @@ -2135,6 +2677,7 @@ proc keys {} { } toplevel $w wm title $w [mc "Gitk key bindings"] + make_transient $w . message $w.m -text " [mc "Gitk key bindings:"] @@ -2163,7 +2706,7 @@ proc keys {} { [mc "<%s-F> Find" $M1T] [mc "<%s-G> Move to next find hit" $M1T] [mc " Move to next find hit"] -[mc "/ Move to next find hit, or redo find"] +[mc "/ Focus the search box"] [mc "? Move to previous find hit"] [mc "f Scroll diff view to next file"] [mc "<%s-S> Search for next hit in diff view" $M1T] @@ -2177,6 +2720,7 @@ proc keys {} { -justify left -bg white -border 2 -relief groove pack $w.m -side top -fill both -padx 2 -pady 2 button $w.ok -text [mc "Close"] -command "destroy $w" -default active + bind $w [list destroy $w] pack $w.ok -side bottom bind $w "focus $w.ok" bind $w "destroy $w" @@ -2357,7 +2901,7 @@ proc treeopendir {w dir} { $w insert e:$ix $e [highlight_tag $de] } } - $w mark gravity e:$ix left + $w mark gravity e:$ix right $w conf -state disabled set treediropen($dir) 1 set top [lindex [split [$w index @0,0] .] 0] @@ -2398,9 +2942,15 @@ proc treeclick {w x y} { } proc setfilelist {id} { - global treefilelist cflist + global treefilelist cflist jump_to_here treeview $cflist $treefilelist($id) 0 + if {$jump_to_here ne {}} { + set f [lindex $jump_to_here 0] + if {[lsearch -exact $treefilelist($id) $f] >= 0} { + showfile $f + } + } } image create bitmap tri-rt -background black -foreground blue -data { @@ -2466,113 +3016,545 @@ image create bitmap reficon-H -background black -foreground green \ image create bitmap reficon-o -background black -foreground "#ddddff" \ -data $rectdata -maskdata $rectmask -proc init_flist {first} { - global cflist cflist_top difffilestart +proc init_flist {first} { + global cflist cflist_top difffilestart + + $cflist conf -state normal + $cflist delete 0.0 end + if {$first ne {}} { + $cflist insert end $first + set cflist_top 1 + $cflist tag add highlight 1.0 "1.0 lineend" + } else { + catch {unset cflist_top} + } + $cflist conf -state disabled + set difffilestart {} +} + +proc highlight_tag {f} { + global highlight_paths + + foreach p $highlight_paths { + if {[string match $p $f]} { + return "bold" + } + } + return {} +} + +proc highlight_filelist {} { + global cmitmode cflist + + $cflist conf -state normal + if {$cmitmode ne "tree"} { + set end [lindex [split [$cflist index end] .] 0] + for {set l 2} {$l < $end} {incr l} { + set line [$cflist get $l.0 "$l.0 lineend"] + if {[highlight_tag $line] ne {}} { + $cflist tag add bold $l.0 "$l.0 lineend" + } + } + } else { + highlight_tree 2 {} + } + $cflist conf -state disabled +} + +proc unhighlight_filelist {} { + global cflist + + $cflist conf -state normal + $cflist tag remove bold 1.0 end + $cflist conf -state disabled +} + +proc add_flist {fl} { + global cflist + + $cflist conf -state normal + foreach f $fl { + $cflist insert end "\n" + $cflist insert end $f [highlight_tag $f] + } + $cflist conf -state disabled +} + +proc sel_flist {w x y} { + global ctext difffilestart cflist cflist_top cmitmode + + if {$cmitmode eq "tree"} return + if {![info exists cflist_top]} return + set l [lindex [split [$w index "@$x,$y"] "."] 0] + $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend" + $cflist tag add highlight $l.0 "$l.0 lineend" + set cflist_top $l + if {$l == 1} { + $ctext yview 1.0 + } else { + catch {$ctext yview [lindex $difffilestart [expr {$l - 2}]]} + } +} + +proc pop_flist_menu {w X Y x y} { + global ctext cflist cmitmode flist_menu flist_menu_file + global treediffs diffids + + stopfinding + set l [lindex [split [$w index "@$x,$y"] "."] 0] + if {$l <= 1} return + if {$cmitmode eq "tree"} { + set e [linetoelt $l] + if {[string index $e end] eq "/"} return + } else { + set e [lindex $treediffs($diffids) [expr {$l-2}]] + } + set flist_menu_file $e + set xdiffstate "normal" + if {$cmitmode eq "tree"} { + set xdiffstate "disabled" + } + # Disable "External diff" item in tree mode + $flist_menu entryconf 2 -state $xdiffstate + tk_popup $flist_menu $X $Y +} + +proc find_ctext_fileinfo {line} { + global ctext_file_names ctext_file_lines + + set ok [bsearch $ctext_file_lines $line] + set tline [lindex $ctext_file_lines $ok] + + if {$ok >= [llength $ctext_file_lines] || $line < $tline} { + return {} + } else { + return [list [lindex $ctext_file_names $ok] $tline] + } +} + +proc pop_diff_menu {w X Y x y} { + global ctext diff_menu flist_menu_file + global diff_menu_txtpos diff_menu_line + global diff_menu_filebase + + set diff_menu_txtpos [split [$w index "@$x,$y"] "."] + set diff_menu_line [lindex $diff_menu_txtpos 0] + # don't pop up the menu on hunk-separator or file-separator lines + if {[lsearch -glob [$ctext tag names $diff_menu_line.0] "*sep"] >= 0} { + return + } + stopfinding + set f [find_ctext_fileinfo $diff_menu_line] + if {$f eq {}} return + set flist_menu_file [lindex $f 0] + set diff_menu_filebase [lindex $f 1] + tk_popup $diff_menu $X $Y +} + +proc flist_hl {only} { + global flist_menu_file findstring gdttype + + set x [shellquote $flist_menu_file] + if {$only || $findstring eq {} || $gdttype ne [mc "touching paths:"]} { + set findstring $x + } else { + append findstring " " $x + } + set gdttype [mc "touching paths:"] +} + +proc save_file_from_commit {filename output what} { + global nullfile + + if {[catch {exec git show $filename -- > $output} err]} { + if {[string match "fatal: bad revision *" $err]} { + return $nullfile + } + error_popup "[mc "Error getting \"%s\" from %s:" $filename $what] $err" + return {} + } + return $output +} + +proc external_diff_get_one_file {diffid filename diffdir} { + global nullid nullid2 nullfile + global gitdir + + if {$diffid == $nullid} { + set difffile [file join [file dirname $gitdir] $filename] + if {[file exists $difffile]} { + return $difffile + } + return $nullfile + } + if {$diffid == $nullid2} { + set difffile [file join $diffdir "\[index\] [file tail $filename]"] + return [save_file_from_commit :$filename $difffile index] + } + set difffile [file join $diffdir "\[$diffid\] [file tail $filename]"] + return [save_file_from_commit $diffid:$filename $difffile \ + "revision $diffid"] +} + +proc external_diff {} { + global gitktmpdir nullid nullid2 + global flist_menu_file + global diffids + global diffnum + global gitdir extdifftool + + if {[llength $diffids] == 1} { + # no reference commit given + set diffidto [lindex $diffids 0] + if {$diffidto eq $nullid} { + # diffing working copy with index + set diffidfrom $nullid2 + } elseif {$diffidto eq $nullid2} { + # diffing index with HEAD + set diffidfrom "HEAD" + } else { + # use first parent commit + global parentlist selectedline + set diffidfrom [lindex $parentlist $selectedline 0] + } + } else { + set diffidfrom [lindex $diffids 0] + set diffidto [lindex $diffids 1] + } + + # make sure that several diffs wont collide + if {![info exists gitktmpdir]} { + set gitktmpdir [file join [file dirname $gitdir] \ + [format ".gitk-tmp.%s" [pid]]] + if {[catch {file mkdir $gitktmpdir} err]} { + error_popup "[mc "Error creating temporary directory %s:" $gitktmpdir] $err" + unset gitktmpdir + return + } + set diffnum 0 + } + incr diffnum + set diffdir [file join $gitktmpdir $diffnum] + if {[catch {file mkdir $diffdir} err]} { + error_popup "[mc "Error creating temporary directory %s:" $diffdir] $err" + return + } + + # gather files to diff + set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir] + set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir] + + if {$difffromfile ne {} && $difftofile ne {}} { + set cmd [list [shellsplit $extdifftool] $difffromfile $difftofile] + if {[catch {set fl [open |$cmd r]} err]} { + file delete -force $diffdir + error_popup "$extdifftool: [mc "command failed:"] $err" + } else { + fconfigure $fl -blocking 0 + filerun $fl [list delete_at_eof $fl $diffdir] + } + } +} + +proc find_hunk_blamespec {base line} { + global ctext + + # Find and parse the hunk header + set s_lix [$ctext search -backwards -regexp ^@@ "$line.0 lineend" $base.0] + if {$s_lix eq {}} return + + set s_line [$ctext get $s_lix "$s_lix + 1 lines"] + if {![regexp {^@@@*(( -\d+(,\d+)?)+) \+(\d+)(,\d+)? @@} $s_line \ + s_line old_specs osz osz1 new_line nsz]} { + return + } + + # base lines for the parents + set base_lines [list $new_line] + foreach old_spec [lrange [split $old_specs " "] 1 end] { + if {![regexp -- {-(\d+)(,\d+)?} $old_spec \ + old_spec old_line osz]} { + return + } + lappend base_lines $old_line + } + + # Now scan the lines to determine offset within the hunk + set max_parent [expr {[llength $base_lines]-2}] + set dline 0 + set s_lno [lindex [split $s_lix "."] 0] + + # Determine if the line is removed + set chunk [$ctext get $line.0 "$line.1 + $max_parent chars"] + if {[string match {[-+ ]*} $chunk]} { + set removed_idx [string first "-" $chunk] + # Choose a parent index + if {$removed_idx >= 0} { + set parent $removed_idx + } else { + set unchanged_idx [string first " " $chunk] + if {$unchanged_idx >= 0} { + set parent $unchanged_idx + } else { + # blame the current commit + set parent -1 + } + } + # then count other lines that belong to it + for {set i $line} {[incr i -1] > $s_lno} {} { + set chunk [$ctext get $i.0 "$i.1 + $max_parent chars"] + # Determine if the line is removed + set removed_idx [string first "-" $chunk] + if {$parent >= 0} { + set code [string index $chunk $parent] + if {$code eq "-" || ($removed_idx < 0 && $code ne "+")} { + incr dline + } + } else { + if {$removed_idx < 0} { + incr dline + } + } + } + incr parent + } else { + set parent 0 + } + + incr dline [lindex $base_lines $parent] + return [list $parent $dline] +} + +proc external_blame_diff {} { + global currentid cmitmode + global diff_menu_txtpos diff_menu_line + global diff_menu_filebase flist_menu_file - $cflist conf -state normal - $cflist delete 0.0 end - if {$first ne {}} { - $cflist insert end $first - set cflist_top 1 - $cflist tag add highlight 1.0 "1.0 lineend" + if {$cmitmode eq "tree"} { + set parent_idx 0 + set line [expr {$diff_menu_line - $diff_menu_filebase}] } else { - catch {unset cflist_top} + set hinfo [find_hunk_blamespec $diff_menu_filebase $diff_menu_line] + if {$hinfo ne {}} { + set parent_idx [lindex $hinfo 0] + set line [lindex $hinfo 1] + } else { + set parent_idx 0 + set line 0 + } } - $cflist conf -state disabled - set difffilestart {} -} -proc highlight_tag {f} { - global highlight_paths + external_blame $parent_idx $line +} - foreach p $highlight_paths { - if {[string match $p $f]} { - return "bold" +# Find the SHA1 ID of the blob for file $fname in the index +# at stage 0 or 2 +proc index_sha1 {fname} { + set f [open [list | git ls-files -s $fname] r] + while {[gets $f line] >= 0} { + set info [lindex [split $line "\t"] 0] + set stage [lindex $info 2] + if {$stage eq "0" || $stage eq "2"} { + close $f + return [lindex $info 1] } } + close $f return {} } -proc highlight_filelist {} { - global cmitmode cflist - - $cflist conf -state normal - if {$cmitmode ne "tree"} { - set end [lindex [split [$cflist index end] .] 0] - for {set l 2} {$l < $end} {incr l} { - set line [$cflist get $l.0 "$l.0 lineend"] - if {[highlight_tag $line] ne {}} { - $cflist tag add bold $l.0 "$l.0 lineend" - } +# Turn an absolute path into one relative to the current directory +proc make_relative {f} { + set elts [file split $f] + set here [file split [pwd]] + set ei 0 + set hi 0 + set res {} + foreach d $here { + if {$ei < $hi || $ei >= [llength $elts] || [lindex $elts $ei] ne $d} { + lappend res ".." + } else { + incr ei } - } else { - highlight_tree 2 {} + incr hi } - $cflist conf -state disabled + set elts [concat $res [lrange $elts $ei end]] + return [eval file join $elts] } -proc unhighlight_filelist {} { - global cflist +proc external_blame {parent_idx {line {}}} { + global flist_menu_file gitdir + global nullid nullid2 + global parentlist selectedline currentid - $cflist conf -state normal - $cflist tag remove bold 1.0 end - $cflist conf -state disabled -} + if {$parent_idx > 0} { + set base_commit [lindex $parentlist $selectedline [expr {$parent_idx-1}]] + } else { + set base_commit $currentid + } -proc add_flist {fl} { - global cflist + if {$base_commit eq {} || $base_commit eq $nullid || $base_commit eq $nullid2} { + error_popup [mc "No such commit"] + return + } - $cflist conf -state normal - foreach f $fl { - $cflist insert end "\n" - $cflist insert end $f [highlight_tag $f] + set cmdline [list git gui blame] + if {$line ne {} && $line > 1} { + lappend cmdline "--line=$line" + } + set f [file join [file dirname $gitdir] $flist_menu_file] + # Unfortunately it seems git gui blame doesn't like + # being given an absolute path... + set f [make_relative $f] + lappend cmdline $base_commit $f + if {[catch {eval exec $cmdline &} err]} { + error_popup "[mc "git gui blame: command failed:"] $err" } - $cflist conf -state disabled } -proc sel_flist {w x y} { - global ctext difffilestart cflist cflist_top cmitmode +proc show_line_source {} { + global cmitmode currentid parents curview blamestuff blameinst + global diff_menu_line diff_menu_filebase flist_menu_file + global nullid nullid2 gitdir - if {$cmitmode eq "tree"} return - if {![info exists cflist_top]} return - set l [lindex [split [$w index "@$x,$y"] "."] 0] - $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend" - $cflist tag add highlight $l.0 "$l.0 lineend" - set cflist_top $l - if {$l == 1} { - $ctext yview 1.0 + set from_index {} + if {$cmitmode eq "tree"} { + set id $currentid + set line [expr {$diff_menu_line - $diff_menu_filebase}] } else { - catch {$ctext yview [lindex $difffilestart [expr {$l - 2}]]} + set h [find_hunk_blamespec $diff_menu_filebase $diff_menu_line] + if {$h eq {}} return + set pi [lindex $h 0] + if {$pi == 0} { + mark_ctext_line $diff_menu_line + return + } + incr pi -1 + if {$currentid eq $nullid} { + if {$pi > 0} { + # must be a merge in progress... + if {[catch { + # get the last line from .git/MERGE_HEAD + set f [open [file join $gitdir MERGE_HEAD] r] + set id [lindex [split [read $f] "\n"] end-1] + close $f + } err]} { + error_popup [mc "Couldn't read merge head: %s" $err] + return + } + } elseif {$parents($curview,$currentid) eq $nullid2} { + # need to do the blame from the index + if {[catch { + set from_index [index_sha1 $flist_menu_file] + } err]} { + error_popup [mc "Error reading index: %s" $err] + return + } + } else { + set id $parents($curview,$currentid) + } + } else { + set id [lindex $parents($curview,$currentid) $pi] + } + set line [lindex $h 1] + } + set blameargs {} + if {$from_index ne {}} { + lappend blameargs | git cat-file blob $from_index } + lappend blameargs | git blame -p -L$line,+1 + if {$from_index ne {}} { + lappend blameargs --contents - + } else { + lappend blameargs $id + } + lappend blameargs -- [file join [file dirname $gitdir] $flist_menu_file] + if {[catch { + set f [open $blameargs r] + } err]} { + error_popup [mc "Couldn't start git blame: %s" $err] + return + } + nowbusy blaming [mc "Searching"] + fconfigure $f -blocking 0 + set i [reg_instance $f] + set blamestuff($i) {} + set blameinst $i + filerun $f [list read_line_source $f $i] } -proc pop_flist_menu {w X Y x y} { - global ctext cflist cmitmode flist_menu flist_menu_file - global treediffs diffids +proc stopblaming {} { + global blameinst - stopfinding - set l [lindex [split [$w index "@$x,$y"] "."] 0] - if {$l <= 1} return - if {$cmitmode eq "tree"} { - set e [linetoelt $l] - if {[string index $e end] eq "/"} return - } else { - set e [lindex $treediffs($diffids) [expr {$l-2}]] + if {[info exists blameinst]} { + stop_instance $blameinst + unset blameinst + notbusy blaming } - set flist_menu_file $e - tk_popup $flist_menu $X $Y } -proc flist_hl {only} { - global flist_menu_file findstring gdttype +proc read_line_source {fd inst} { + global blamestuff curview commfd blameinst nullid nullid2 - set x [shellquote $flist_menu_file] - if {$only || $findstring eq {} || $gdttype ne [mc "touching paths:"]} { - set findstring $x + while {[gets $fd line] >= 0} { + lappend blamestuff($inst) $line + } + if {![eof $fd]} { + return 1 + } + unset commfd($inst) + unset blameinst + notbusy blaming + fconfigure $fd -blocking 1 + if {[catch {close $fd} err]} { + error_popup [mc "Error running git blame: %s" $err] + return 0 + } + + set fname {} + set line [split [lindex $blamestuff($inst) 0] " "] + set id [lindex $line 0] + set lnum [lindex $line 1] + if {[string length $id] == 40 && [string is xdigit $id] && + [string is digit -strict $lnum]} { + # look for "filename" line + foreach l $blamestuff($inst) { + if {[string match "filename *" $l]} { + set fname [string range $l 9 end] + break + } + } + } + if {$fname ne {}} { + # all looks good, select it + if {$id eq $nullid} { + # blame uses all-zeroes to mean not committed, + # which would mean a change in the index + set id $nullid2 + } + if {[commitinview $id $curview]} { + selectline [rowofcommit $id] 1 [list $fname $lnum] + } else { + error_popup [mc "That line comes from commit %s, \ + which is not in this view" [shortids $id]] + } } else { - append findstring " " $x + puts "oops couldn't parse git blame output" } - set gdttype [mc "touching paths:"] + return 0 +} + +# delete $dir when we see eof on $f (presumably because the child has exited) +proc delete_at_eof {f dir} { + while {[gets $f line] >= 0} {} + if {[eof $f]} { + if {[catch {close $f} err]} { + error_popup "[mc "External diff viewer failed:"] $err" + } + file delete -force $dir + return 0 + } + return 1 } # Functions for adding and removing shell-type quoting @@ -2672,8 +3654,8 @@ proc shellsplit {str} { # Code to implement multiple views proc newview {ishighlight} { - global nextviewnum newviewname newviewperm newishighlight - global newviewargs revtreeargs + global nextviewnum newviewname newishighlight + global revtreeargs viewargscmd newviewopts curview set newishighlight $ishighlight set top .gitkview @@ -2681,49 +3663,184 @@ proc newview {ishighlight} { raise $top return } - set newviewname($nextviewnum) "View $nextviewnum" - set newviewperm($nextviewnum) 0 - set newviewargs($nextviewnum) [shellarglist $revtreeargs] + set newviewname($nextviewnum) "[mc "View"] $nextviewnum" + set newviewopts($nextviewnum,perm) 0 + set newviewopts($nextviewnum,cmd) $viewargscmd($curview) + decode_view_opts $nextviewnum $revtreeargs vieweditor $top $nextviewnum [mc "Gitk view definition"] } +set known_view_options { + {perm b . {} {mc "Remember this view"}} + {args t50= + {} {mc "Commits to include (arguments to git log):"}} + {all b * "--all" {mc "Use all refs"}} + {dorder b . {"--date-order" "-d"} {mc "Strictly sort by date"}} + {lright b . "--left-right" {mc "Mark branch sides"}} + {since t15 + {"--since=*" "--after=*"} {mc "Since date:"}} + {until t15 . {"--until=*" "--before=*"} {mc "Until date:"}} + {limit t10 + "--max-count=*" {mc "Max count:"}} + {skip t10 . "--skip=*" {mc "Skip:"}} + {first b . "--first-parent" {mc "Limit to first parent"}} + {cmd t50= + {} {mc "Command to generate more commits to include:"}} + } + +proc encode_view_opts {n} { + global known_view_options newviewopts + + set rargs [list] + foreach opt $known_view_options { + set patterns [lindex $opt 3] + if {$patterns eq {}} continue + set pattern [lindex $patterns 0] + + set val $newviewopts($n,[lindex $opt 0]) + + if {[lindex $opt 1] eq "b"} { + if {$val} { + lappend rargs $pattern + } + } else { + set val [string trim $val] + if {$val ne {}} { + set pfix [string range $pattern 0 end-1] + lappend rargs $pfix$val + } + } + } + return [concat $rargs [shellsplit $newviewopts($n,args)]] +} + +proc decode_view_opts {n view_args} { + global known_view_options newviewopts + + foreach opt $known_view_options { + if {[lindex $opt 1] eq "b"} { + set val 0 + } else { + set val {} + } + set newviewopts($n,[lindex $opt 0]) $val + } + set oargs [list] + foreach arg $view_args { + if {[regexp -- {^-([0-9]+)$} $arg arg cnt] + && ![info exists found(limit)]} { + set newviewopts($n,limit) $cnt + set found(limit) 1 + continue + } + catch { unset val } + foreach opt $known_view_options { + set id [lindex $opt 0] + if {[info exists found($id)]} continue + foreach pattern [lindex $opt 3] { + if {![string match $pattern $arg]} continue + if {[lindex $opt 1] ne "b"} { + set size [string length $pattern] + set val [string range $arg [expr {$size-1}] end] + } else { + set val 1 + } + set newviewopts($n,$id) $val + set found($id) 1 + break + } + if {[info exists val]} break + } + if {[info exists val]} continue + lappend oargs $arg + } + set newviewopts($n,args) [shellarglist $oargs] +} + +proc edit_or_newview {} { + global curview + + if {$curview > 0} { + editview + } else { + newview 0 + } +} + proc editview {} { global curview - global viewname viewperm newviewname newviewperm - global viewargs newviewargs + global viewname viewperm newviewname newviewopts + global viewargs viewargscmd set top .gitkvedit-$curview if {[winfo exists $top]} { raise $top return } - set newviewname($curview) $viewname($curview) - set newviewperm($curview) $viewperm($curview) - set newviewargs($curview) [shellarglist $viewargs($curview)] - vieweditor $top $curview "Gitk: edit view $viewname($curview)" + set newviewname($curview) $viewname($curview) + set newviewopts($curview,perm) $viewperm($curview) + set newviewopts($curview,cmd) $viewargscmd($curview) + decode_view_opts $curview $viewargs($curview) + vieweditor $top $curview "[mc "Gitk: edit view"] $viewname($curview)" } proc vieweditor {top n title} { - global newviewname newviewperm viewfiles bgcolor + global newviewname newviewopts viewfiles bgcolor + global known_view_options toplevel $top wm title $top $title + make_transient $top . + + # View name + frame $top.nfr label $top.nl -text [mc "Name"] entry $top.name -width 20 -textvariable newviewname($n) - grid $top.nl $top.name -sticky w -pady 5 - checkbutton $top.perm -text [mc "Remember this view"] \ - -variable newviewperm($n) - grid $top.perm - -pady 5 -sticky w - message $top.al -aspect 1000 \ - -text [mc "Commits to include (arguments to git log):"] - grid $top.al - -sticky w -pady 5 - entry $top.args -width 50 -textvariable newviewargs($n) \ - -background $bgcolor - grid $top.args - -sticky ew -padx 5 - message $top.l -aspect 1000 \ + pack $top.nfr -in $top -fill x -pady 5 -padx 3 + pack $top.nl -in $top.nfr -side left -padx {0 30} + pack $top.name -in $top.nfr -side left + + # View options + set cframe $top.nfr + set cexpand 0 + set cnt 0 + foreach opt $known_view_options { + set id [lindex $opt 0] + set type [lindex $opt 1] + set flags [lindex $opt 2] + set title [eval [lindex $opt 4]] + set lxpad 0 + + if {$flags eq "+" || $flags eq "*"} { + set cframe $top.fr$cnt + incr cnt + frame $cframe + pack $cframe -in $top -fill x -pady 3 -padx 3 + set cexpand [expr {$flags eq "*"}] + } else { + set lxpad 5 + } + + if {$type eq "b"} { + checkbutton $cframe.c_$id -text $title -variable newviewopts($n,$id) + pack $cframe.c_$id -in $cframe -side left \ + -padx [list $lxpad 0] -expand $cexpand -anchor w + } elseif {[regexp {^t(\d+)$} $type type sz]} { + message $cframe.l_$id -aspect 1500 -text $title + entry $cframe.e_$id -width $sz -background $bgcolor \ + -textvariable newviewopts($n,$id) + pack $cframe.l_$id -in $cframe -side left -padx [list $lxpad 0] + pack $cframe.e_$id -in $cframe -side left -expand 1 -fill x + } elseif {[regexp {^t(\d+)=$} $type type sz]} { + message $cframe.l_$id -aspect 1500 -text $title + entry $cframe.e_$id -width $sz -background $bgcolor \ + -textvariable newviewopts($n,$id) + pack $cframe.l_$id -in $cframe -side top -pady [list 3 0] -anchor w + pack $cframe.e_$id -in $cframe -side top -fill x + } + } + + # Path list + message $top.l -aspect 1500 \ -text [mc "Enter files and directories to include, one per line:"] - grid $top.l - -sticky w - text $top.t -width 40 -height 10 -background $bgcolor -font uifont + pack $top.l -in $top -side top -pady [list 7 0] -anchor w -padx 3 + text $top.t -width 40 -height 5 -background $bgcolor -font uifont if {[info exists viewfiles($n)]} { foreach f $viewfiles($n) { $top.t insert end $f @@ -2732,14 +3849,19 @@ proc vieweditor {top n title} { $top.t delete {end - 1c} end $top.t mark set insert 0.0 } - grid $top.t - -sticky ew -padx 5 + pack $top.t -in $top -side top -pady [list 0 5] -fill both -expand 1 -padx 3 frame $top.buts button $top.buts.ok -text [mc "OK"] -command [list newviewok $top $n] + button $top.buts.apply -text [mc "Apply (F5)"] -command [list newviewok $top $n 1] button $top.buts.can -text [mc "Cancel"] -command [list destroy $top] - grid $top.buts.ok $top.buts.can + bind $top [list newviewok $top $n] + bind $top [list newviewok $top $n 1] + bind $top [list destroy $top] + grid $top.buts.ok $top.buts.apply $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 + grid columnconfigure $top.buts 2 -weight 1 -uniform a + pack $top.buts -in $top -side top -fill x focus $top.t } @@ -2760,17 +3882,15 @@ proc allviewmenus {n op args} { # doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args } -proc newviewok {top n} { +proc newviewok {top n {apply 0}} { global nextviewnum newviewperm newviewname newishighlight global viewname viewfiles viewperm selectedview curview - global viewargs newviewargs viewhlmenu + global viewargs viewargscmd newviewopts viewhlmenu if {[catch { - set newargs [shellsplit $newviewargs($n)] + set newargs [encode_view_opts $n] } err]} { - error_popup "[mc "Error in commit selection arguments:"] $err" - wm raise $top - focus $top + error_popup "[mc "Error in commit selection arguments:"] $err" $top return } set files {} @@ -2784,9 +3904,10 @@ proc newviewok {top n} { # creating a new view incr nextviewnum set viewname($n) $newviewname($n) - set viewperm($n) $newviewperm($n) + set viewperm($n) $newviewopts($n,perm) set viewfiles($n) $files set viewargs($n) $newargs + set viewargscmd($n) $newviewopts($n,cmd) addviewmenu $n if {!$newishighlight} { run showview $n @@ -2795,7 +3916,7 @@ proc newviewok {top n} { } } else { # editing an existing view - set viewperm($n) $newviewperm($n) + set viewperm($n) $newviewopts($n,perm) if {$newviewname($n) ne $viewname($n)} { set viewname($n) $newviewname($n) doviewmenu .bar.view 5 [list showview $n] \ @@ -2803,14 +3924,17 @@ proc newviewok {top n} { # doviewmenu $viewhlmenu 1 [list addvhighlight $n] \ # entryconf [list -label $viewname($n) -value $viewname($n)] } - if {$files ne $viewfiles($n) || $newargs ne $viewargs($n)} { + if {$files ne $viewfiles($n) || $newargs ne $viewargs($n) || \ + $newviewopts($n,cmd) ne $viewargscmd($n)} { set viewfiles($n) $files set viewargs($n) $newargs + set viewargscmd($n) $newviewopts($n,cmd) if {$curview == $n} { run reloadcommits } } } + if {$apply} return catch {destroy $top} } @@ -2837,7 +3961,7 @@ proc addviewmenu {n} { } proc showview {n} { - global curview viewfiles cached_commitrow ordertok + global curview cached_commitrow ordertok global displayorder parentlist rowidlist rowisopt rowfinal global colormap rowtextx nextcolor canvxmax global numcommits viewcomplete @@ -2855,7 +3979,7 @@ proc showview {n} { set ytop [expr {[lindex $span 0] * $ymax}] set ybot [expr {[lindex $span 1] * $ymax}] set yscreen [expr {($ybot - $ytop) / 2}] - if {[info exists selectedline]} { + if {$selectedline ne {}} { set selid $currentid set y [yc $selectedline] if {$ytop < $y && $y < $ybot} { @@ -2879,15 +4003,12 @@ proc showview {n} { set curview $n set selectedview $n - .bar.view entryconf [mc "Edit view..."] -state [expr {$n == 0? "disabled": "normal"}] - .bar.view entryconf [mc "Delete view"] -state [expr {$n == 0? "disabled": "normal"}] + .bar.view entryconf [mca "Edit view..."] -state [expr {$n == 0? "disabled": "normal"}] + .bar.view entryconf [mca "Delete view"] -state [expr {$n == 0? "disabled": "normal"}] run refill_reflist if {![info exists viewcomplete($n)]} { - if {$selid ne {}} { - set pending_select $selid - } - getcommits + getcommits $selid return } @@ -2921,18 +4042,18 @@ proc showview {n} { drawvisible if {$row ne {}} { selectline $row 0 - } elseif {$mainheadid ne {} && [commitinview $mainheadid $curview]} { - selectline [rowofcommit $mainheadid] 1 } elseif {!$viewcomplete($n)} { - if {$selid ne {}} { - set pending_select $selid - } else { - set pending_select $mainheadid - } + reset_pending_select $selid } else { - set row [first_real_row] - if {$row < $numcommits} { - selectline $row 0 + reset_pending_select {} + + if {[commitinview $pending_select $curview]} { + selectline [rowofcommit $pending_select] 1 + } else { + set row [first_real_row] + if {$row < $numcommits} { + selectline $row 0 + } } } if {!$viewcomplete($n)} { @@ -2964,28 +4085,34 @@ proc ishighlighted {id} { return 0 } -proc bolden {row font} { - global canv linehtag selectedline boldrows +proc bolden {id font} { + global canv linehtag currentid boldids need_redisplay markedid - lappend boldrows $row - $canv itemconf $linehtag($row) -font $font - if {[info exists selectedline] && $row == $selectedline} { + # need_redisplay = 1 means the display is stale and about to be redrawn + if {$need_redisplay} return + lappend boldids $id + $canv itemconf $linehtag($id) -font $font + if {[info exists currentid] && $id eq $currentid} { $canv delete secsel - set t [eval $canv create rect [$canv bbox $linehtag($row)] \ + set t [eval $canv create rect [$canv bbox $linehtag($id)] \ -outline {{}} -tags secsel \ -fill [$canv cget -selectbackground]] $canv lower $t } + if {[info exists markedid] && $id eq $markedid} { + make_idmark $id + } } -proc bolden_name {row font} { - global canv2 linentag selectedline boldnamerows +proc bolden_name {id font} { + global canv2 linentag currentid boldnameids need_redisplay - lappend boldnamerows $row - $canv2 itemconf $linentag($row) -font $font - if {[info exists selectedline] && $row == $selectedline} { + if {$need_redisplay} return + lappend boldnameids $id + $canv2 itemconf $linentag($id) -font $font + if {[info exists currentid] && $id eq $currentid} { $canv2 delete secsel - set t [eval $canv2 create rect [$canv2 bbox $linentag($row)] \ + set t [eval $canv2 create rect [$canv2 bbox $linentag($id)] \ -outline {{}} -tags secsel \ -fill [$canv2 cget -selectbackground]] $canv2 lower $t @@ -2993,17 +4120,17 @@ proc bolden_name {row font} { } proc unbolden {} { - global boldrows + global boldids set stillbold {} - foreach row $boldrows { - if {![ishighlighted [commitonrow $row]]} { - bolden $row mainfont + foreach id $boldids { + if {![ishighlighted $id]} { + bolden $id mainfont } else { - lappend stillbold $row + lappend stillbold $id } } - set boldrows $stillbold + set boldids $stillbold } proc addvhighlight {n} { @@ -3044,7 +4171,7 @@ proc vhighlightmore {} { set row [rowofcommit $id] if {$r0 <= $row && $row <= $r1} { if {![highlighted $row]} { - bolden $row mainfontbold + bolden $id mainfontbold } set vhighlights($id) 1 } @@ -3059,7 +4186,7 @@ proc askvhighlight {row id} { if {[commitinview $id $hlview]} { if {[info exists iddrawn($id)] && ![ishighlighted $id]} { - bolden $row mainfontbold + bolden $id mainfontbold } set vhighlights($id) 1 } else { @@ -3069,7 +4196,7 @@ proc askvhighlight {row id} { proc hfiles_change {} { global highlight_files filehighlight fhighlights fh_serial - global highlight_paths gdttype + global highlight_paths if {[info exists filehighlight]} { # delete previous highlights @@ -3127,15 +4254,15 @@ proc find_change {name ix op} { } proc findcom_change args { - global nhighlights boldnamerows + global nhighlights boldnameids global findpattern findtype findstring gdttype stopfinding # delete previous highlights, if any - foreach row $boldnamerows { - bolden_name $row mainfont + foreach id $boldnameids { + bolden_name $id mainfont } - set boldnamerows {} + set boldnameids {} catch {unset nhighlights} unbolden unmarkmatches @@ -3224,9 +4351,8 @@ proc readfhighlight {} { set fhl_list [lrange $fhl_list [expr {$i+1}] end] if {$line eq {}} continue if {![commitinview $line $curview]} continue - set row [rowofcommit $line] if {[info exists iddrawn($line)] && ![ishighlighted $line]} { - bolden $row mainfontbold + bolden $line mainfontbold } set fhighlights($line) 1 } @@ -3278,9 +4404,9 @@ proc askfindhighlight {row id} { } if {$isbold && [info exists iddrawn($id)]} { if {![ishighlighted $id]} { - bolden $row mainfontbold + bolden $id mainfontbold if {$isbold > 1} { - bolden_name $row mainfontbold + bolden_name $id mainfontbold } } if {$markingmatches} { @@ -3300,15 +4426,15 @@ proc markrowmatches {row id} { if {$findloc eq [mc "All fields"] || $findloc eq [mc "Headline"]} { set m [findmatches $headline] if {$m ne {}} { - markmatches $canv $row $headline $linehtag($row) $m \ - [$canv itemcget $linehtag($row) -font] $row + markmatches $canv $row $headline $linehtag($id) $m \ + [$canv itemcget $linehtag($id) -font] $row } } if {$findloc eq [mc "All fields"] || $findloc eq [mc "Author"]} { set m [findmatches $author] if {$m ne {}} { - markmatches $canv2 $row $author $linentag($row) $m \ - [$canv2 itemcget $linentag($row) -font] $row + markmatches $canv2 $row $author $linentag($id) $m \ + [$canv2 itemcget $linentag($id) -font] $row } } } @@ -3412,7 +4538,7 @@ proc askrelhighlight {row id} { global descendent highlight_related iddrawn rhighlights global selectedline ancestor - if {![info exists selectedline]} return + if {$selectedline eq {}} return set isbold 0 if {$highlight_related eq [mc "Descendant"] || $highlight_related eq [mc "Not descendant"]} { @@ -3433,7 +4559,7 @@ proc askrelhighlight {row id} { } if {[info exists iddrawn($id)]} { if {$isbold && ![ishighlighted $id]} { - bolden $row mainfontbold + bolden $id mainfontbold } } set rhighlights($id) $isbold @@ -3586,8 +4712,8 @@ proc visiblerows {} { proc layoutmore {} { global commitidx viewcomplete curview - global numcommits pending_select selectedline curview - global lastscrollset lastscrollrows commitinterest + global numcommits pending_select curview + global lastscrollset lastscrollrows if {$lastscrollrows < 100 || $viewcomplete($curview) || [clock clicks -milliseconds] - $lastscrollset > 500} { @@ -3595,18 +4721,62 @@ proc layoutmore {} { } if {[info exists pending_select] && [commitinview $pending_select $curview]} { + update selectline [rowofcommit $pending_select] 1 } drawvisible } +# With path limiting, we mightn't get the actual HEAD commit, +# so ask git rev-list what is the first ancestor of HEAD that +# touches a file in the path limit. +proc get_viewmainhead {view} { + global viewmainheadid vfilelimit viewinstances mainheadid + + catch { + set rfd [open [concat | git rev-list -1 $mainheadid \ + -- $vfilelimit($view)] r] + set j [reg_instance $rfd] + lappend viewinstances($view) $j + fconfigure $rfd -blocking 0 + filerun $rfd [list getviewhead $rfd $j $view] + set viewmainheadid($curview) {} + } +} + +# git rev-list should give us just 1 line to use as viewmainheadid($view) +proc getviewhead {fd inst view} { + global viewmainheadid commfd curview viewinstances showlocalchanges + + set id {} + if {[gets $fd line] < 0} { + if {![eof $fd]} { + return 1 + } + } elseif {[string length $line] == 40 && [string is xdigit $line]} { + set id $line + } + set viewmainheadid($view) $id + close $fd + unset commfd($inst) + set i [lsearch -exact $viewinstances($view) $inst] + if {$i >= 0} { + set viewinstances($view) [lreplace $viewinstances($view) $i $i] + } + if {$showlocalchanges && $id ne {} && $view == $curview} { + doshowlocalchanges + } + return 0 +} + proc doshowlocalchanges {} { - global curview mainheadid + global curview viewmainheadid - if {[commitinview $mainheadid $curview]} { + if {$viewmainheadid($curview) eq {}} return + if {[commitinview $viewmainheadid($curview) $curview]} { dodiffindex } else { - lappend commitinterest($mainheadid) {dodiffindex} + interestedin $viewmainheadid($curview) dodiffindex } } @@ -3624,17 +4794,24 @@ proc dohidelocalchanges {} { # spawn off a process to do git diff-index --cached HEAD proc dodiffindex {} { - global lserial showlocalchanges + global lserial showlocalchanges vfilelimit curview + global isworktree - if {!$showlocalchanges} return + if {!$showlocalchanges || !$isworktree} return incr lserial - set fd [open "|git diff-index --cached HEAD" r] + set cmd "|git diff-index --cached HEAD" + if {$vfilelimit($curview) ne {}} { + set cmd [concat $cmd -- $vfilelimit($curview)] + } + set fd [open $cmd r] fconfigure $fd -blocking 0 - filerun $fd [list readdiffindex $fd $lserial] + set i [reg_instance $fd] + filerun $fd [list readdiffindex $fd $lserial $i] } -proc readdiffindex {fd serial} { - global mainheadid nullid nullid2 curview commitinfo commitdata lserial +proc readdiffindex {fd serial inst} { + global viewmainheadid nullid nullid2 curview commitinfo commitdata lserial + global vfilelimit set isdiff 1 if {[gets $fd line] < 0} { @@ -3644,16 +4821,21 @@ proc readdiffindex {fd serial} { set isdiff 0 } # we only need to see one line and we don't really care what it says... - close $fd + stop_instance $inst if {$serial != $lserial} { return 0 } # now see if there are any local changes not checked in to the index - set fd [open "|git diff-files" r] + set cmd "|git diff-files" + if {$vfilelimit($curview) ne {}} { + set cmd [concat $cmd -- $vfilelimit($curview)] + } + set fd [open $cmd r] fconfigure $fd -blocking 0 - filerun $fd [list readdifffiles $fd $serial] + set i [reg_instance $fd] + filerun $fd [list readdifffiles $fd $serial $i] if {$isdiff && ![commitinview $nullid2 $curview]} { # add the line for the changes in the index to the graph @@ -3663,15 +4845,18 @@ proc readdiffindex {fd serial} { if {[commitinview $nullid $curview]} { removefakerow $nullid } - insertfakerow $nullid2 $mainheadid + insertfakerow $nullid2 $viewmainheadid($curview) } elseif {!$isdiff && [commitinview $nullid2 $curview]} { + if {[commitinview $nullid $curview]} { + removefakerow $nullid + } removefakerow $nullid2 } return 0 } -proc readdifffiles {fd serial} { - global mainheadid nullid nullid2 curview +proc readdifffiles {fd serial inst} { + global viewmainheadid nullid nullid2 curview global commitinfo commitdata lserial set isdiff 1 @@ -3682,7 +4867,7 @@ proc readdifffiles {fd serial} { set isdiff 0 } # we only need to see one line and we don't really care what it says... - close $fd + stop_instance $inst if {$serial != $lserial} { return 0 @@ -3696,7 +4881,7 @@ proc readdifffiles {fd serial} { if {[commitinview $nullid2 $curview]} { set p $nullid2 } else { - set p $mainheadid + set p $viewmainheadid($curview) } insertfakerow $nullid $p } elseif {!$isdiff && [commitinview $nullid $curview]} { @@ -4421,7 +5606,8 @@ proc drawcmittext {id row col} { global cmitlisted commitinfo rowidlist parentlist global rowtextx idpos idtags idheads idotherrefs global linehtag linentag linedtag selectedline - global canvxmax boldrows boldnamerows fgcolor nullid nullid2 + global canvxmax boldids boldnameids fgcolor markedid + global mainheadid nullid nullid2 circleitem circlecolors ctxbut # listed is 0 for boundary, 1 for normal, 2 for negative, 3 for left, 4 for right set listed $cmitlisted($curview,$id) @@ -4429,8 +5615,10 @@ proc drawcmittext {id row col} { set ofill red } elseif {$id eq $nullid2} { set ofill green + } elseif {$id eq $mainheadid} { + set ofill yellow } else { - set ofill [expr {$listed != 0 ? $listed == 2 ? "gray" : "blue" : "white"}] + set ofill [lindex $circlecolors $listed] } set x [xc $row $col] set y [yc $row] @@ -4454,6 +5642,7 @@ proc drawcmittext {id row col} { [expr {$x - $orad}] [expr {$y + $orad - 1}] \ -fill $ofill -outline $fgcolor -width 1 -tags circle] } + set circleitem($row) $t $canv raise $t $canv bind $t <1> {selcanvline {} %x %y} set rmx [llength [lindex $rowidlist $row]] @@ -4482,22 +5671,25 @@ proc drawcmittext {id row col} { set nfont mainfont set isbold [ishighlighted $id] if {$isbold > 0} { - lappend boldrows $row + lappend boldids $id set font mainfontbold if {$isbold > 1} { - lappend boldnamerows $row + lappend boldnameids $id set nfont mainfontbold } } - 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 -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] - if {[info exists selectedline] && $selectedline == $row} { - make_secsel $row + set linehtag($id) [$canv create text $xt $y -anchor w -fill $fgcolor \ + -text $headline -font $font -tags text] + $canv bind $linehtag($id) $ctxbut "rowmenu %X %Y $id" + set linentag($id) [$canv2 create text 3 $y -anchor w -fill $fgcolor \ + -text $name -font $nfont -tags text] + set linedtag($id) [$canv3 create text 3 $y -anchor w -fill $fgcolor \ + -text $date -font mainfont -tags text] + if {$selectedline == $row} { + make_secsel $id + } + if {[info exists markedid] && $markedid eq $id} { + make_idmark $id } set xr [expr {$xt + [font measure $font $headline]}] if {$xr > $canvxmax} { @@ -4588,7 +5780,6 @@ proc drawcommits {row {endrow {}}} { optimize_rows $ro1 0 $r2 if {$need_redisplay || $nrows_drawn > 2000} { clear_display - drawvisible } # make the lines join to already-drawn rows either side @@ -4687,7 +5878,7 @@ proc drawvisible {} { if {$endrow >= $vrowmod($curview)} { update_arcrows $curview } - if {[info exists selectedline] && + if {$selectedline ne {} && $row <= $selectedline && $selectedline <= $endrow} { set targetrow $selectedline } elseif {[info exists targetid]} { @@ -4705,10 +5896,16 @@ proc drawvisible {} { proc clear_display {} { global iddrawn linesegs need_redisplay nrows_drawn global vhighlights fhighlights nhighlights rhighlights + global linehtag linentag linedtag boldids boldnameids allcanvs delete all catch {unset iddrawn} catch {unset linesegs} + catch {unset linehtag} + catch {unset linentag} + catch {unset linedtag} + set boldids {} + set boldnameids {} catch {unset vhighlights} catch {unset fhighlights} catch {unset nhighlights} @@ -4827,7 +6024,7 @@ proc bindline {t id} { proc drawtags {id x xt y1} { global idtags idheads idotherrefs mainhead global linespc lthickness - global canv rowtextx curview fgcolor bgcolor + global canv rowtextx curview fgcolor bgcolor ctxbut set marks {} set ntags 0 @@ -4905,7 +6102,7 @@ proc drawtags {id x xt y1} { if {$ntags >= 0} { $canv bind $t <1> [list showtag $tag 1] } elseif {$nheads >= 0} { - $canv bind $t [list headmenu %X %Y $id $tag] + $canv bind $t $ctxbut [list headmenu %X %Y $id $tag] } } return $xt @@ -5003,7 +6200,7 @@ proc dofind {{dirn 1} {wrap 1}} { } focus . if {$findstring eq {} || $numcommits == 0} return - if {![info exists selectedline]} { + if {$selectedline eq {}} { set findstartline [lindex [visiblerows] [expr {$dirn < 0}]] } else { set findstartline $selectedline @@ -5029,6 +6226,7 @@ proc stopfinding {} { set fprogcoord 0 adjustprogress } + stopblaming } proc findmore {} { @@ -5165,10 +6363,11 @@ proc findmore {} { proc findselectline {l} { global findloc commentend ctext findcurline markingmatches gdttype - set markingmatches 1 + set markingmatches [expr {$gdttype eq [mc "containing:"]}] set findcurline $l selectline $l 1 - if {$findloc == [mc "All fields"] || $findloc == [mc "Comments"]} { + if {$markingmatches && + ($findloc eq [mc "All fields"] || $findloc eq [mc "Comments"])} { # highlight the matches in the comments set f [$ctext get 1.0 $commentend] set matches [findmatches $f] @@ -5199,7 +6398,7 @@ proc markmatches {canv l str tag matches font row} { [expr {$x0+$xlen+2}] $y1 \ -outline {} -tags [list match$l matches] -fill yellow] $canv lower $t - if {[info exists selectedline] && $row == $selectedline} { + if {$row == $selectedline} { $canv raise $t secsel } } @@ -5248,11 +6447,11 @@ proc commit_descriptor {p} { # append some text to the ctext widget, and make any SHA1 ID # that we know about be a clickable link. proc appendwithlinks {text tags} { - global ctext linknum curview pendinglinks + global ctext linknum curview set start [$ctext index "end - 1c"] $ctext insert end $text $tags - set links [regexp -indices -all -inline {[0-9a-f]{40}} $text] + set links [regexp -indices -all -inline {\m[0-9a-f]{6,40}\M} $text] foreach l $links { set s [lindex $l 0] set e [lindex $l 1] @@ -5266,16 +6465,27 @@ proc appendwithlinks {text tags} { } proc setlink {id lk} { - global curview ctext pendinglinks commitinterest + global curview ctext pendinglinks - if {[commitinview $id $curview]} { + set known 0 + if {[string length $id] < 40} { + set matches [longid $id] + if {[llength $matches] > 0} { + if {[llength $matches] > 1} return + set known 1 + set id [lindex $matches 0] + } + } else { + set known [commitinview $id $curview] + } + if {$known} { $ctext tag conf $lk -foreground blue -underline 1 - $ctext tag bind $lk <1> [list selectline [rowofcommit $id] 1] + $ctext tag bind $lk <1> [list selbyid $id] $ctext tag bind $lk {linkcursor %W 1} $ctext tag bind $lk {linkcursor %W -1} } else { lappend pendinglinks($id) $lk - lappend commitinterest($id) {makelink %I} + interestedin $id {makelink %P} } } @@ -5335,7 +6545,7 @@ proc appendrefs {pos ids var} { } } if {[llength $tags] > $maxrefs} { - $ctext insert $pos "many ([llength $tags])" + $ctext insert $pos "[mc "many"] ([llength $tags])" } else { set tags [lsort -index 0 -decreasing $tags] set sep {} @@ -5358,7 +6568,7 @@ proc appendrefs {pos ids var} { proc dispneartags {delay} { global selectedline currentid showneartags tagphase - if {![info exists selectedline] || !$showneartags} return + if {$selectedline eq {} || !$showneartags} return after cancel dispnexttag if {$delay} { after 200 dispnexttag @@ -5372,7 +6582,7 @@ proc dispneartags {delay} { proc dispnexttag {} { global selectedline currentid showneartags tagphase ctext - if {![info exists selectedline] || !$showneartags} return + if {$selectedline eq {} || !$showneartags} return switch -- $tagphase { 0 { set dtags [desctags $currentid] @@ -5404,25 +6614,35 @@ proc dispnexttag {} { } } -proc make_secsel {l} { +proc make_secsel {id} { global linehtag linentag linedtag canv canv2 canv3 - if {![info exists linehtag($l)]} return + if {![info exists linehtag($id)]} return $canv delete secsel - set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \ + set t [eval $canv create rect [$canv bbox $linehtag($id)] -outline {{}} \ -tags secsel -fill [$canv cget -selectbackground]] $canv lower $t $canv2 delete secsel - set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \ + set t [eval $canv2 create rect [$canv2 bbox $linentag($id)] -outline {{}} \ -tags secsel -fill [$canv2 cget -selectbackground]] $canv2 lower $t $canv3 delete secsel - set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \ + set t [eval $canv3 create rect [$canv3 bbox $linedtag($id)] -outline {{}} \ -tags secsel -fill [$canv3 cget -selectbackground]] $canv3 lower $t } -proc selectline {l isnew} { +proc make_idmark {id} { + global linehtag canv fgcolor + + if {![info exists linehtag($id)]} return + $canv delete markid + set t [eval $canv create rect [$canv bbox $linehtag($id)] \ + -tags markid -outline $fgcolor] + $canv raise $t +} + +proc selectline {l isnew {desired_loc {}}} { global canv ctext commitinfo selectedline global canvy0 linespc parents children curview global currentid sha1entry @@ -5430,6 +6650,7 @@ proc selectline {l isnew} { global mergemax numcommits pending_select global cmitmode showneartags allcommits global targetrow targetid lastscrollrows + global autoselect jump_to_here catch {unset pending_select} $canv delete hover @@ -5482,7 +6703,7 @@ proc selectline {l isnew} { drawvisible } - make_secsel $l + make_secsel $id if {$isnew} { addtohistory [list selbyid $id] @@ -5490,8 +6711,10 @@ proc selectline {l isnew} { $sha1entry delete 0 end $sha1entry insert 0 $id - $sha1entry selection from 0 - $sha1entry selection to end + if {$autoselect} { + $sha1entry selection from 0 + $sha1entry selection to end + } rhighlight_sel $id $ctext conf -state normal @@ -5566,6 +6789,7 @@ proc selectline {l isnew} { $ctext conf -state disabled set commentend [$ctext index "end - 1c"] + set jump_to_here $desired_loc init_flist [mc "Comments"] if {$cmitmode eq "tree"} { gettree $id @@ -5591,7 +6815,7 @@ proc sellastline {} { proc selnextline {dir} { global selectedline focus . - if {![info exists selectedline]} return + if {$selectedline eq {}} return set l [expr {$selectedline + $dir}] unmarkmatches selectline $l 1 @@ -5606,7 +6830,7 @@ proc selnextpage {dir} { } allcanvs yview scroll [expr {$dir * $lpp}] units drawvisible - if {![info exists selectedline]} return + if {$selectedline eq {}} return set l [expr {$selectedline + $dir * $lpp}] if {$l < 0} { set l 0 @@ -5620,7 +6844,7 @@ proc selnextpage {dir} { proc unselectline {} { global selectedline currentid - catch {unset selectedline} + set selectedline {} catch {unset currentid} allcanvs delete secsel rhighlight_none @@ -5629,7 +6853,7 @@ proc unselectline {} { proc reselectline {} { global selectedline - if {[info exists selectedline]} { + if {$selectedline ne {}} { selectline $selectedline 0 } } @@ -5718,7 +6942,7 @@ proc gettree {id} { set treepending $id set treefilelist($id) {} set treeidlist($id) {} - fconfigure $gtf -blocking 0 + fconfigure $gtf -blocking 0 -encoding binary filerun $gtf [list gettreeline $gtf $id] } } else { @@ -5734,16 +6958,18 @@ proc gettreeline {gtf id} { if {$diffids eq $nullid} { set fname $line } else { - if {$diffids ne $nullid2 && [lindex $line 1] ne "blob"} continue set i [string first "\t" $line] if {$i < 0} continue - set sha1 [lindex $line 2] set fname [string range $line [expr {$i+1}] end] - if {[string index $fname 0] eq "\""} { - set fname [lindex $fname 0] - } + set line [string range $line 0 [expr {$i-1}]] + if {$diffids ne $nullid2 && [lindex $line 1] ne "blob"} continue + set sha1 [lindex $line 2] lappend treeidlist($id) $sha1 } + if {[string index $fname 0] eq "\""} { + set fname [lindex $fname 0] + } + set fname [encoding convertfrom $fname] lappend treefilelist($id) $fname } if {![eof $gtf]} { @@ -5765,6 +6991,7 @@ proc gettreeline {gtf id} { proc showfile {f} { global treefilelist treeidlist diffids nullid nullid2 + global ctext_file_names ctext_file_lines global ctext commentend set i [lsearch -exact $treefilelist($diffids) $f] @@ -5784,10 +7011,12 @@ proc showfile {f} { return } } - fconfigure $bf -blocking 0 + fconfigure $bf -blocking 0 -encoding [get_path_encoding $f] filerun $bf [list getblobline $bf $diffids] $ctext config -state normal clear_ctext $commentend + lappend ctext_file_names $f + lappend ctext_file_lines [lindex [split $commentend "."] 0] $ctext insert end "\n" $ctext insert end "$f\n" filesep $ctext config -state disabled @@ -5808,109 +7037,43 @@ proc getblobline {bf id} { $ctext insert end "$line\n" } if {[eof $bf]} { + global jump_to_here ctext_file_names commentend + # delete last newline $ctext delete "end - 2c" "end - 1c" close $bf + if {$jump_to_here ne {} && + [lindex $jump_to_here 0] eq [lindex $ctext_file_names 0]} { + set lnum [expr {[lindex $jump_to_here 1] + + [lindex [split $commentend .] 0]}] + mark_ctext_line $lnum + } return 0 } $ctext config -state disabled return [expr {$nl >= 1000? 2: 1}] } -proc mergediff {id} { - global diffmergeid mdifffd - global diffids - global parents - global diffcontext - global limitdiffs viewfiles curview +proc mark_ctext_line {lnum} { + global ctext markbgcolor - set diffmergeid $id - set diffids $id - # this doesn't seem to actually affect anything... - set cmd [concat | git diff-tree --no-commit-id --cc -U$diffcontext $id] - if {$limitdiffs && $viewfiles($curview) ne {}} { - set cmd [concat $cmd -- $viewfiles($curview)] - } - if {[catch {set mdf [open $cmd r]} err]} { - error_popup "[mc "Error getting merge diffs:"] $err" - return - } - fconfigure $mdf -blocking 0 - set mdifffd($id) $mdf - set np [llength $parents($curview,$id)] - settabs $np - filerun $mdf [list getmergediffline $mdf $id $np] + $ctext tag delete omark + $ctext tag add omark $lnum.0 "$lnum.0 + 1 line" + $ctext tag conf omark -background $markbgcolor + $ctext see $lnum.0 } - -proc getmergediffline {mdf id np} { - global diffmergeid ctext cflist mergemax - global difffilestart mdifffd - - $ctext conf -state normal - set nr 0 - while {[incr nr] <= 1000 && [gets $mdf line] >= 0} { - if {![info exists diffmergeid] || $id != $diffmergeid - || $mdf != $mdifffd($id)} { - close $mdf - return 0 - } - if {[regexp {^diff --cc (.*)} $line match fname]} { - # start of a new file - $ctext insert end "\n" - set here [$ctext index "end - 1c"] - lappend difffilestart $here - add_flist [list $fname] - set l [expr {(78 - [string length $fname]) / 2}] - set pad [string range "----------------------------------------" 1 $l] - $ctext insert end "$pad $fname $pad\n" filesep - } elseif {[regexp {^@@} $line]} { - $ctext insert end "$line\n" hunksep - } elseif {[regexp {^[0-9a-f]{40}$} $line] || [regexp {^index} $line]} { - # do nothing - } else { - # parse the prefix - one ' ', '-' or '+' for each parent - set spaces {} - set minuses {} - set pluses {} - set isbad 0 - for {set j 0} {$j < $np} {incr j} { - set c [string range $line $j $j] - if {$c == " "} { - lappend spaces $j - } elseif {$c == "-"} { - lappend minuses $j - } elseif {$c == "+"} { - lappend pluses $j - } else { - set isbad 1 - break - } - } - set tags {} - set num {} - if {!$isbad && $minuses ne {} && $pluses eq {}} { - # line doesn't appear in result, parents in $minuses have the line - set num [lindex $minuses 0] - } elseif {!$isbad && $pluses ne {} && $minuses eq {}} { - # line appears in result, parents in $pluses don't have the line - lappend tags mresult - set num [lindex $spaces 0] - } - if {$num ne {}} { - if {$num >= $mergemax} { - set num "max" - } - lappend tags m$num - } - $ctext insert end "$line\n" $tags - } - } - $ctext conf -state disabled - if {[eof $mdf]} { - close $mdf - return 0 - } - return [expr {$nr >= 1000? 2: 1}] + +proc mergediff {id} { + global diffmergeid + global diffids treediffs + global parents curview + + set diffmergeid $id + set diffids $id + set treediffs($id) {} + set np [llength $parents($curview,$id)] + settabs $np + getblobdiffs $id } proc startdiff {ids} { @@ -5998,36 +7161,54 @@ proc diffcmd {ids flags} { proc gettreediffs {ids} { global treediff treepending + if {[catch {set gdtf [open [diffcmd $ids {--no-commit-id}] r]}]} return + set treepending $ids set treediff {} - if {[catch {set gdtf [open [diffcmd $ids {--no-commit-id}] r]}]} return - fconfigure $gdtf -blocking 0 + fconfigure $gdtf -blocking 0 -encoding binary filerun $gdtf [list gettreediffline $gdtf $ids] } proc gettreediffline {gdtf ids} { global treediff treediffs treepending diffids diffmergeid - global cmitmode viewfiles curview limitdiffs + global cmitmode vfilelimit curview limitdiffs perfile_attrs set nr 0 - while {[incr nr] <= 1000 && [gets $gdtf line] >= 0} { + set sublist {} + set max 1000 + if {$perfile_attrs} { + # cache_gitattr is slow, and even slower on win32 where we + # have to invoke it for only about 30 paths at a time + set max 500 + if {[tk windowingsystem] == "win32"} { + set max 120 + } + } + while {[incr nr] <= $max && [gets $gdtf line] >= 0} { set i [string first "\t" $line] if {$i >= 0} { set file [string range $line [expr {$i+1}] end] if {[string index $file 0] eq "\""} { set file [lindex $file 0] } - lappend treediff $file + set file [encoding convertfrom $file] + if {$file ne [lindex $treediff end]} { + lappend treediff $file + lappend sublist $file + } } } + if {$perfile_attrs} { + cache_gitattr encoding $sublist + } if {![eof $gdtf]} { - return [expr {$nr >= 1000? 2: 1}] + return [expr {$nr >= $max? 2: 1}] } close $gdtf - if {$limitdiffs && $viewfiles($curview) ne {}} { + if {$limitdiffs && $vfilelimit($curview) ne {}} { set flist {} foreach f $treediff { - if {[path_filter $viewfiles($curview) $f]} { + if {[path_filter $vfilelimit($curview) $f]} { lappend flist $f } } @@ -6036,7 +7217,7 @@ proc gettreediffline {gdtf ids} { set treediffs($ids) $treediff } unset treepending - if {$cmitmode eq "tree"} { + if {$cmitmode eq "tree" && [llength $diffids] == 1} { gettree $diffids } elseif {$ids != $diffids} { if {![info exists diffmergeid]} { @@ -6073,21 +7254,25 @@ proc getblobdiffs {ids} { global diffinhdr treediffs global diffcontext global ignorespace - global limitdiffs viewfiles curview + global limitdiffs vfilelimit curview + global diffencoding targetline diffnparents - set cmd [diffcmd $ids "-p -C --no-commit-id -U$diffcontext"] + set cmd [diffcmd $ids "-p -C --cc --no-commit-id -U$diffcontext"] if {$ignorespace} { append cmd " -w" } - if {$limitdiffs && $viewfiles($curview) ne {}} { - set cmd [concat $cmd -- $viewfiles($curview)] + if {$limitdiffs && $vfilelimit($curview) ne {}} { + set cmd [concat $cmd -- $vfilelimit($curview)] } if {[catch {set bdf [open $cmd r]} err]} { - puts "error getting diffs: $err" + error_popup [mc "Error getting diffs: %s" $err] return } + set targetline {} + set diffnparents 0 set diffinhdr 0 - fconfigure $bdf -blocking 0 + set diffencoding [get_path_encoding {}] + fconfigure $bdf -blocking 0 -encoding binary -eofchar {} set blobdifffd($ids) $bdf filerun $bdf [list getblobdiffline $bdf $diffids] } @@ -6106,21 +7291,32 @@ proc setinlist {var i val} { } proc makediffhdr {fname ids} { - global ctext curdiffstart treediffs + global ctext curdiffstart treediffs diffencoding + global ctext_file_names jump_to_here targetline diffline + set fname [encoding convertfrom $fname] + set diffencoding [get_path_encoding $fname] set i [lsearch -exact $treediffs($ids) $fname] if {$i >= 0} { setinlist difffilestart $i $curdiffstart } + lset ctext_file_names end $fname set l [expr {(78 - [string length $fname]) / 2}] set pad [string range "----------------------------------------" 1 $l] $ctext insert $curdiffstart "$pad $fname $pad" filesep + set targetline {} + if {$jump_to_here ne {} && [lindex $jump_to_here 0] eq $fname} { + set targetline [lindex $jump_to_here 1] + } + set diffline 0 } proc getblobdiffline {bdf ids} { global diffids blobdifffd ctext curdiffstart global diffnexthead diffnextnote difffilestart - global diffinhdr treediffs + global ctext_file_names ctext_file_lines + global diffinhdr treediffs mergemax diffnparents + global diffencoding jump_to_here targetline diffline set nr 0 $ctext conf -state normal @@ -6129,40 +7325,74 @@ proc getblobdiffline {bdf ids} { close $bdf return 0 } - if {![string compare -length 11 "diff --git " $line]} { - # trim off "diff --git " - set line [string range $line 11 end] - set diffinhdr 1 + if {![string compare -length 5 "diff " $line]} { + if {![regexp {^diff (--cc|--git) } $line m type]} { + set line [encoding convertfrom $line] + $ctext insert end "$line\n" hunksep + continue + } # start of a new file + set diffinhdr 1 $ctext insert end "\n" set curdiffstart [$ctext index "end - 1c"] + lappend ctext_file_names "" + lappend ctext_file_lines [lindex [split $curdiffstart "."] 0] $ctext insert end "\n" filesep - # If the name hasn't changed the length will be odd, - # the middle char will be a space, and the two bits either - # side will be a/name and b/name, or "a/name" and "b/name". - # If the name has changed we'll get "rename from" and - # "rename to" or "copy from" and "copy to" lines following this, - # and we'll use them to get the filenames. - # This complexity is necessary because spaces in the filename(s) - # don't get escaped. - set l [string length $line] - set i [expr {$l / 2}] - if {!(($l & 1) && [string index $line $i] eq " " && - [string range $line 2 [expr {$i - 1}]] eq \ - [string range $line [expr {$i + 3}] end])} { - continue - } - # unescape if quoted and chop off the a/ from the front - if {[string index $line 0] eq "\""} { - set fname [string range [lindex $line 0] 2 end] + + if {$type eq "--cc"} { + # start of a new file in a merge diff + set fname [string range $line 10 end] + if {[lsearch -exact $treediffs($ids) $fname] < 0} { + lappend treediffs($ids) $fname + add_flist [list $fname] + } + } else { - set fname [string range $line 2 [expr {$i - 1}]] + set line [string range $line 11 end] + # If the name hasn't changed the length will be odd, + # the middle char will be a space, and the two bits either + # side will be a/name and b/name, or "a/name" and "b/name". + # If the name has changed we'll get "rename from" and + # "rename to" or "copy from" and "copy to" lines following + # this, and we'll use them to get the filenames. + # This complexity is necessary because spaces in the + # filename(s) don't get escaped. + set l [string length $line] + set i [expr {$l / 2}] + if {!(($l & 1) && [string index $line $i] eq " " && + [string range $line 2 [expr {$i - 1}]] eq \ + [string range $line [expr {$i + 3}] end])} { + continue + } + # unescape if quoted and chop off the a/ from the front + if {[string index $line 0] eq "\""} { + set fname [string range [lindex $line 0] 2 end] + } else { + set fname [string range $line 2 [expr {$i - 1}]] + } } makediffhdr $fname $ids - } elseif {[regexp {^@@ -([0-9]+)(,[0-9]+)? \+([0-9]+)(,[0-9]+)? @@(.*)} \ - $line match f1l f1c f2l f2c rest]} { + } elseif {![string compare -length 16 "* Unmerged path " $line]} { + set fname [encoding convertfrom [string range $line 16 end]] + $ctext insert end "\n" + set curdiffstart [$ctext index "end - 1c"] + lappend ctext_file_names $fname + lappend ctext_file_lines [lindex [split $curdiffstart "."] 0] + $ctext insert end "$line\n" filesep + set i [lsearch -exact $treediffs($ids) $fname] + if {$i >= 0} { + setinlist difffilestart $i $curdiffstart + } + + } elseif {![string compare -length 2 "@@" $line]} { + regexp {^@@+} $line ats + set line [encoding convertfrom $diffencoding $line] $ctext insert end "$line\n" hunksep + if {[regexp { \+(\d+),\d+ @@} $line m nl]} { + set diffline $nl + } + set diffnparents [expr {[string length $ats] - 1}] set diffinhdr 0 } elseif {$diffinhdr} { @@ -6171,6 +7401,7 @@ proc getblobdiffline {bdf ids} { if {[string index $fname 0] eq "\""} { set fname [lindex $fname 0] } + set fname [encoding convertfrom $fname] set i [lsearch -exact $treediffs($ids) $fname] if {$i >= 0} { setinlist difffilestart $i $curdiffstart @@ -6192,12 +7423,45 @@ proc getblobdiffline {bdf ids} { $ctext insert end "$line\n" filesep } else { - set x [string range $line 0 0] - if {$x == "-" || $x == "+"} { - set tag [expr {$x == "+"}] - $ctext insert end "$line\n" d$tag - } elseif {$x == " "} { - $ctext insert end "$line\n" + set line [string map {\x1A ^Z} \ + [encoding convertfrom $diffencoding $line]] + # parse the prefix - one ' ', '-' or '+' for each parent + set prefix [string range $line 0 [expr {$diffnparents - 1}]] + set tag [expr {$diffnparents > 1? "m": "d"}] + if {[string trim $prefix " -+"] eq {}} { + # prefix only has " ", "-" and "+" in it: normal diff line + set num [string first "-" $prefix] + if {$num >= 0} { + # removed line, first parent with line is $num + if {$num >= $mergemax} { + set num "max" + } + $ctext insert end "$line\n" $tag$num + } else { + set tags {} + if {[string first "+" $prefix] >= 0} { + # added line + lappend tags ${tag}result + if {$diffnparents > 1} { + set num [string first " " $prefix] + if {$num >= 0} { + if {$num >= $mergemax} { + set num "max" + } + lappend tags m$num + } + } + } + if {$targetline ne {}} { + if {$diffline == $targetline} { + set seehere [$ctext index "end - 1 chars"] + set targetline {} + } else { + incr diffline + } + } + $ctext insert end "$line\n" $tags + } } else { # "\ No newline at end of file", # or something else we don't recognize @@ -6205,6 +7469,9 @@ proc getblobdiffline {bdf ids} { } } } + if {[info exists seehere]} { + mark_ctext_line [lindex [split $seehere .] 0] + } $ctext conf -state disabled if {[eof $bdf]} { close $bdf @@ -6217,29 +7484,47 @@ proc changediffdisp {} { global ctext diffelide $ctext tag conf d0 -elide [lindex $diffelide 0] - $ctext tag conf d1 -elide [lindex $diffelide 1] + $ctext tag conf dresult -elide [lindex $diffelide 1] +} + +proc highlightfile {loc cline} { + global ctext cflist cflist_top + + $ctext yview $loc + $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend" + $cflist tag add highlight $cline.0 "$cline.0 lineend" + $cflist see $cline.0 + set cflist_top $cline } proc prevfile {} { - global difffilestart ctext - set prev [lindex $difffilestart 0] + global difffilestart ctext cmitmode + + if {$cmitmode eq "tree"} return + set prev 0.0 + set prevline 1 set here [$ctext index @0,0] foreach loc $difffilestart { if {[$ctext compare $loc >= $here]} { - $ctext yview $prev + highlightfile $prev $prevline return } set prev $loc + incr prevline } - $ctext yview $prev + highlightfile $prev $prevline } proc nextfile {} { - global difffilestart ctext + global difffilestart ctext cmitmode + + if {$cmitmode eq "tree"} return set here [$ctext index @0,0] + set line 1 foreach loc $difffilestart { + incr line if {[$ctext compare $loc > $here]} { - $ctext yview $loc + highlightfile $loc $line return } } @@ -6247,6 +7532,7 @@ proc nextfile {} { proc clear_ctext {{first 1.0}} { global ctext smarktop smarkbot + global ctext_file_names ctext_file_lines global pendinglinks set l [lindex [split $first .] 0] @@ -6260,6 +7546,8 @@ proc clear_ctext {{first 1.0}} { if {$first eq "1.0"} { catch {unset pendinglinks} } + set ctext_file_names {} + set ctext_file_lines {} } proc settabs {{firstab {}}} { @@ -6392,7 +7680,7 @@ proc searchmarkvisible {doall} { proc scrolltext {f0 f1} { global searchstring - .bleft.sb set $f0 $f1 + .bleft.bottom.sb set $f0 $f1 if {$searchstring ne {}} { searchmarkvisible 0 } @@ -6422,7 +7710,7 @@ proc redisplay {} { setcanvscroll allcanvs yview moveto [lindex $span 0] drawvisible - if {[info exists selectedline]} { + if {$selectedline ne {}} { selectline $selectedline 0 allcanvs yview moveto [lindex $span 0] } @@ -6535,13 +7823,13 @@ proc gotocommit {} { } else { set id [string tolower $sha1string] if {[regexp {^[0-9a-f]{4,39}$} $id]} { - set matches [array names varcid "$curview,$id*"] + set matches [longid $id] if {$matches ne {}} { if {[llength $matches] > 1} { error_popup [mc "Short SHA1 id %s is ambiguous" $id] return } - set id [lindex [split [lindex $matches 0] ","] 1] + set id [lindex $matches 0] } } } @@ -6739,32 +8027,183 @@ proc mstime {} { proc rowmenu {x y id} { global rowctxmenu selectedline rowmenuid curview - global nullid nullid2 fakerowmenu mainhead + global nullid nullid2 fakerowmenu mainhead markedid stopfinding set rowmenuid $id - if {![info exists selectedline] - || [rowofcommit $id] eq $selectedline} { + if {$selectedline eq {} || [rowofcommit $id] eq $selectedline} { set state disabled } else { set state normal } if {$id ne $nullid && $id ne $nullid2} { set menu $rowctxmenu - $menu entryconfigure 7 -label [mc "Reset %s branch to here" $mainhead] + if {$mainhead ne {}} { + $menu entryconfigure 7 -label [mc "Reset %s branch to here" $mainhead] -state normal + } else { + $menu entryconfigure 7 -label [mc "Detached head: can't reset" $mainhead] -state disabled + } + if {[info exists markedid] && $markedid ne $id} { + $menu entryconfigure 9 -state normal + $menu entryconfigure 10 -state normal + $menu entryconfigure 11 -state normal + } else { + $menu entryconfigure 9 -state disabled + $menu entryconfigure 10 -state disabled + $menu entryconfigure 11 -state disabled + } } else { set menu $fakerowmenu } - $menu entryconfigure [mc "Diff this -> selected"] -state $state - $menu entryconfigure [mc "Diff selected -> this"] -state $state - $menu entryconfigure [mc "Make patch"] -state $state + $menu entryconfigure [mca "Diff this -> selected"] -state $state + $menu entryconfigure [mca "Diff selected -> this"] -state $state + $menu entryconfigure [mca "Make patch"] -state $state tk_popup $menu $x $y } +proc markhere {} { + global rowmenuid markedid canv + + set markedid $rowmenuid + make_idmark $markedid +} + +proc gotomark {} { + global markedid + + if {[info exists markedid]} { + selbyid $markedid + } +} + +proc replace_by_kids {l r} { + global curview children + + set id [commitonrow $r] + set l [lreplace $l 0 0] + foreach kid $children($curview,$id) { + lappend l [rowofcommit $kid] + } + return [lsort -integer -decreasing -unique $l] +} + +proc find_common_desc {} { + global markedid rowmenuid curview children + + if {![info exists markedid]} return + if {![commitinview $markedid $curview] || + ![commitinview $rowmenuid $curview]} return + #set t1 [clock clicks -milliseconds] + set l1 [list [rowofcommit $markedid]] + set l2 [list [rowofcommit $rowmenuid]] + while 1 { + set r1 [lindex $l1 0] + set r2 [lindex $l2 0] + if {$r1 eq {} || $r2 eq {}} break + if {$r1 == $r2} { + selectline $r1 1 + break + } + if {$r1 > $r2} { + set l1 [replace_by_kids $l1 $r1] + } else { + set l2 [replace_by_kids $l2 $r2] + } + } + #set t2 [clock clicks -milliseconds] + #puts "took [expr {$t2-$t1}]ms" +} + +proc compare_commits {} { + global markedid rowmenuid curview children + + if {![info exists markedid]} return + if {![commitinview $markedid $curview]} return + addtohistory [list do_cmp_commits $markedid $rowmenuid] + do_cmp_commits $markedid $rowmenuid +} + +proc getpatchid {id} { + global patchids + + if {![info exists patchids($id)]} { + set x [exec git diff-tree -p --root $id | git patch-id] + set patchids($id) [lindex $x 0] + } + return $patchids($id) +} + +proc do_cmp_commits {a b} { + global ctext curview parents children patchids commitinfo + + $ctext conf -state normal + clear_ctext + init_flist {} + for {set i 0} {$i < 100} {incr i} { + set shorta [string range $a 0 7] + set shortb [string range $b 0 7] + set skipa 0 + set skipb 0 + if {[llength $parents($curview,$a)] > 1} { + appendwithlinks [mc "Skipping merge commit %s\n" $shorta] {} + set skipa 1 + } else { + set patcha [getpatchid $a] + } + if {[llength $parents($curview,$b)] > 1} { + appendwithlinks [mc "Skipping merge commit %s\n" $shortb] {} + set skipb 1 + } else { + set patchb [getpatchid $b] + } + if {!$skipa && !$skipb} { + set heada [lindex $commitinfo($a) 0] + set headb [lindex $commitinfo($b) 0] + if {$patcha eq $patchb} { + if {$heada eq $headb} { + appendwithlinks [mc "Commit %s == %s %s\n" \ + $shorta $shortb $heada] {} + } else { + appendwithlinks [mc "Commit %s %s\n" $shorta $heada] {} + appendwithlinks [mc " is the same patch as\n"] {} + appendwithlinks [mc " %s %s\n" $shortb $headb] {} + } + set skipa 1 + set skipb 1 + } else { + $ctext insert end "\n" + appendwithlinks [mc "Commit %s %s\n" $shorta $heada] {} + appendwithlinks [mc " differs from\n"] {} + appendwithlinks [mc " %s %s\n" $shortb $headb] {} + appendwithlinks [mc "- stopping\n"] + break + } + } + if {$skipa} { + if {[llength $children($curview,$a)] != 1} { + $ctext insert end "\n" + appendwithlinks [mc "Commit %s has %s children - stopping\n" \ + $shorta [llength $children($curview,$a)]] {} + break + } + set a [lindex $children($curview,$a) 0] + } + if {$skipb} { + if {[llength $children($curview,$b)] != 1} { + appendwithlinks [mc "Commit %s has %s children - stopping\n" \ + $shortb [llength $children($curview,$b)]] {} + break + } + set b [lindex $children($curview,$b) 0] + } + } + $ctext conf -state disabled +} + proc diffvssel {dirn} { global rowmenuid selectedline - if {![info exists selectedline]} return + if {$selectedline eq {}} return if {$dirn} { set oldid [commitonrow $selectedline] set newid $rowmenuid @@ -6811,6 +8250,7 @@ proc mkpatch {} { set patchtop $top catch {destroy $top} toplevel $top + make_transient $top . label $top.title -text [mc "Generate patch"] grid $top.title - -pady 10 label $top.from -text [mc "From:"] @@ -6841,6 +8281,8 @@ proc mkpatch {} { frame $top.buts button $top.buts.gen -text [mc "Generate"] -command mkpatchgo button $top.buts.can -text [mc "Cancel"] -command mkpatchcan + bind $top mkpatchgo + bind $top mkpatchcan grid $top.buts.gen $top.buts.can grid columnconfigure $top.buts 0 -weight 1 -uniform a grid columnconfigure $top.buts 1 -weight 1 -uniform a @@ -6875,7 +8317,7 @@ proc mkpatchgo {} { set cmd [lrange $cmd 1 end] lappend cmd >$fname & if {[catch {eval exec $cmd} err]} { - error_popup "[mc "Error creating patch:"] $err" + error_popup "[mc "Error creating patch:"] $err" $patchtop } catch {destroy $patchtop} unset patchtop @@ -6895,6 +8337,7 @@ proc mktag {} { set mktagtop $top catch {destroy $top} toplevel $top + make_transient $top . label $top.title -text [mc "Create tag"] grid $top.title - -pady 10 label $top.id -text [mc "ID:"] @@ -6912,6 +8355,8 @@ proc mktag {} { frame $top.buts button $top.buts.gen -text [mc "Create"] -command mktaggo button $top.buts.can -text [mc "Cancel"] -command mktagcan + bind $top mktaggo + bind $top mktagcan grid $top.buts.gen $top.buts.can grid columnconfigure $top.buts 0 -weight 1 -uniform a grid columnconfigure $top.buts 1 -weight 1 -uniform a @@ -6925,18 +8370,18 @@ proc domktag {} { set id [$mktagtop.sha1 get] set tag [$mktagtop.tag get] if {$tag == {}} { - error_popup [mc "No tag name specified"] - return + error_popup [mc "No tag name specified"] $mktagtop + return 0 } if {[info exists tagids($tag)]} { - error_popup [mc "Tag \"%s\" already exists" $tag] - return + error_popup [mc "Tag \"%s\" already exists" $tag] $mktagtop + return 0 } if {[catch { exec git tag $tag $id } err]} { - error_popup "[mc "Error creating tag:"] $err" - return + error_popup "[mc "Error creating tag:"] $err" $mktagtop + return 0 } set tagids($tag) $id @@ -6945,27 +8390,37 @@ proc domktag {} { addedtag $id dispneartags 0 run refill_reflist + return 1 } proc redrawtags {id} { - global canv linehtag idpos currentid curview - global canvxmax iddrawn + global canv linehtag idpos currentid curview cmitlisted markedid + global canvxmax iddrawn circleitem mainheadid circlecolors if {![commitinview $id $curview]} return if {![info exists iddrawn($id)]} return set row [rowofcommit $id] + if {$id eq $mainheadid} { + set ofill yellow + } else { + set ofill [lindex $circlecolors $cmitlisted($curview,$id)] + } + $canv itemconf $circleitem($row) -fill $ofill $canv delete tag.$id set xt [eval drawtags $id $idpos($id)] - $canv coords $linehtag($row) $xt [lindex $idpos($id) 2] - set text [$canv itemcget $linehtag($row) -text] - set font [$canv itemcget $linehtag($row) -font] + $canv coords $linehtag($id) $xt [lindex $idpos($id) 2] + set text [$canv itemcget $linehtag($id) -text] + set font [$canv itemcget $linehtag($id) -font] set xr [expr {$xt + [font measure $font $text]}] if {$xr > $canvxmax} { set canvxmax $xr setcanvscroll } if {[info exists currentid] && $currentid == $id} { - make_secsel $row + make_secsel $id + } + if {[info exists markedid] && $markedid eq $id} { + make_idmark $id } } @@ -6977,7 +8432,7 @@ proc mktagcan {} { } proc mktaggo {} { - domktag + if {![domktag]} return mktagcan } @@ -6988,6 +8443,7 @@ proc writecommit {} { set wrcomtop $top catch {destroy $top} toplevel $top + make_transient $top . label $top.title -text [mc "Write commit to file"] grid $top.title - -pady 10 label $top.id -text [mc "ID:"] @@ -7009,6 +8465,8 @@ proc writecommit {} { frame $top.buts button $top.buts.gen -text [mc "Write"] -command wrcomgo button $top.buts.can -text [mc "Cancel"] -command wrcomcan + bind $top wrcomgo + bind $top wrcomcan grid $top.buts.gen $top.buts.can grid columnconfigure $top.buts 0 -weight 1 -uniform a grid columnconfigure $top.buts 1 -weight 1 -uniform a @@ -7023,7 +8481,7 @@ proc wrcomgo {} { set cmd "echo $id | [$wrcomtop.cmd get]" set fname [$wrcomtop.fname get] if {[catch {exec sh -c $cmd >$fname &} err]} { - error_popup "[mc "Error writing commit:"] $err" + error_popup "[mc "Error writing commit:"] $err" $wrcomtop } catch {destroy $wrcomtop} unset wrcomtop @@ -7042,6 +8500,7 @@ proc mkbranch {} { set top .makebranch catch {destroy $top} toplevel $top + make_transient $top . label $top.title -text [mc "Create new branch"] grid $top.title - -pady 10 label $top.id -text [mc "ID:"] @@ -7055,6 +8514,8 @@ proc mkbranch {} { frame $top.buts button $top.buts.go -text [mc "Create"] -command [list mkbrgo $top] button $top.buts.can -text [mc "Cancel"] -command "catch {destroy $top}" + bind $top [list mkbrgo $top] + bind $top "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 @@ -7067,29 +8528,73 @@ proc mkbrgo {top} { set name [$top.name get] set id [$top.sha1 get] + set cmdargs {} + set old_id {} if {$name eq {}} { - error_popup [mc "Please specify a name for the new branch"] + error_popup [mc "Please specify a name for the new branch"] $top return } + if {[info exists headids($name)]} { + if {![confirm_popup [mc \ + "Branch '%s' already exists. Overwrite?" $name] $top]} { + return + } + set old_id $headids($name) + lappend cmdargs -f + } catch {destroy $top} + lappend cmdargs $name $id nowbusy newbranch update if {[catch { - exec git branch $name $id + eval exec git branch $cmdargs } err]} { notbusy newbranch error_popup $err } else { - set headids($name) $id - lappend idheads($id) $name - addedhead $id $name notbusy newbranch - redrawtags $id + if {$old_id ne {}} { + movehead $id $name + movedhead $id $name + redrawtags $old_id + redrawtags $id + } else { + set headids($name) $id + lappend idheads($id) $name + addedhead $id $name + redrawtags $id + } dispneartags 0 run refill_reflist } } +proc exec_citool {tool_args {baseid {}}} { + global commitinfo env + + set save_env [array get env GIT_AUTHOR_*] + + if {$baseid ne {}} { + if {![info exists commitinfo($baseid)]} { + getcommit $baseid + } + set author [lindex $commitinfo($baseid) 1] + set date [lindex $commitinfo($baseid) 2] + if {[regexp {^\s*(\S.*\S|\S)\s*<(.*)>\s*$} \ + $author author name email] + && $date ne {}} { + set env(GIT_AUTHOR_NAME) $name + set env(GIT_AUTHOR_EMAIL) $email + set env(GIT_AUTHOR_DATE) $date + } + } + + eval exec git citool $tool_args & + + array unset env GIT_AUTHOR_* + array set env $save_env +} + proc cherrypick {} { global rowmenuid curview global mainhead mainheadid @@ -7108,7 +8613,26 @@ proc cherrypick {} { # 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 + if {[regexp -line \ + {Entry '(.*)' (would be overwritten by merge|not uptodate)} \ + $err msg fname]} { + error_popup [mc "Cherry-pick failed because of local changes\ + to file '%s'.\nPlease commit, reset or stash\ + your changes and try again." $fname] + } elseif {[regexp -line \ + {^(CONFLICT \(.*\):|Automatic cherry-pick failed)} \ + $err]} { + if {[confirm_popup [mc "Cherry-pick failed because of merge\ + conflict.\nDo you wish to run git citool to\ + resolve it?"]]} { + # Force citool to read MERGE_MSG + file delete [file join [gitdir] "GITGUI_MSG"] + exec_citool {} $rowmenuid + } + } else { + error_popup $err + } + run updatecommits return } set newhead [exec git rev-parse HEAD] @@ -7119,12 +8643,13 @@ proc cherrypick {} { } addnewchild $newhead $oldhead if {[commitinview $oldhead $curview]} { + # XXX this isn't right if we have a path limit... insertrow $newhead $oldhead $curview if {$mainhead ne {}} { movehead $newhead $mainhead movedhead $newhead $mainhead - set mainheadid $newhead } + set mainheadid $newhead redrawtags $oldhead redrawtags $newhead selbyid $newhead @@ -7138,7 +8663,7 @@ proc resethead {} { set confirm_ok 0 set w ".confirmreset" toplevel $w - wm transient $w . + make_transient $w . wm title $w [mc "Confirm reset"] message $w.m -text \ [mc "Reset branch %s to %s?" $mainhead [string range $rowmenuid 0 7]] \ @@ -7161,12 +8686,13 @@ proc resethead {} { button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w" pack $w.ok -side left -fill x -padx 20 -pady 20 button $w.cancel -text [mc Cancel] -command "destroy $w" + bind $w [list destroy $w] pack $w.cancel -side right -fill x -padx 20 -pady 20 bind $w "grab $w; focus $w" tkwait window $w if {!$confirm_ok} return if {[catch {set fd [open \ - [list | sh -c "git reset --$resettype $rowmenuid 2>&1"] r]} err]} { + [list | git reset --$resettype $rowmenuid 2>@1] r]} err]} { error_popup $err } else { dohidelocalchanges @@ -7224,29 +8750,50 @@ proc headmenu {x y id head} { } proc cobranch {} { - global headmenuid headmenuhead mainhead headids - global showlocalchanges mainheadid + global headmenuid headmenuhead headids + global showlocalchanges # check the tree is clean first?? - set oldmainhead $mainhead nowbusy checkout [mc "Checking out"] update dohidelocalchanges if {[catch { - exec git checkout -q $headmenuhead + set fd [open [list | git checkout $headmenuhead 2>@1] r] } err]} { notbusy checkout error_popup $err + if {$showlocalchanges} { + dodiffindex + } } else { - notbusy checkout - set mainhead $headmenuhead - set mainheadid $headmenuid - if {[info exists headids($oldmainhead)]} { - redrawtags $headids($oldmainhead) + filerun $fd [list readcheckoutstat $fd $headmenuhead $headmenuid] + } +} + +proc readcheckoutstat {fd newhead newheadid} { + global mainhead mainheadid headids showlocalchanges progresscoords + global viewmainheadid curview + + if {[gets $fd line] >= 0} { + if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} { + set progresscoords [list 0 [expr {1.0 * $m / $n}]] + adjustprogress } - redrawtags $headmenuid - selbyid $headmenuid + return 1 + } + set progresscoords {0 0} + adjustprogress + notbusy checkout + if {[catch {close $fd} err]} { + error_popup $err } + set oldmainid $mainheadid + set mainhead $newhead + set mainheadid $newheadid + set viewmainheadid($curview) $newheadid + redrawtags $oldmainid + redrawtags $newheadid + selbyid $newheadid if {$showlocalchanges} { dodiffindex } @@ -7298,6 +8845,7 @@ proc showrefs {} { } toplevel $top wm title $top [mc "Tags and heads: %s" [file tail [pwd]]] + make_transient $top . text $top.list -background $bgcolor -foreground $fgcolor \ -selectbackground $selectbgcolor -font mainfont \ -xscrollcommand "$top.xsb set" -yscrollcommand "$top.ysb set" \ @@ -7319,6 +8867,7 @@ proc showrefs {} { pack $top.f.l -side left grid $top.f - -sticky ew -pady 2 button $top.close -command [list destroy $top] -text [mc "Close"] + bind $top [list destroy $top] grid $top.close - grid columnconfigure $top 0 -weight 1 grid rowconfigure $top 0 -weight 1 @@ -7360,7 +8909,7 @@ proc reflistfilter_change {n1 n2 op} { proc refill_reflist {} { global reflist reflistfilter showrefstop headids tagids otherrefids - global curview commitinterest + global curview if {![info exists showrefstop] || ![winfo exists $showrefstop]} return set refs {} @@ -7369,7 +8918,7 @@ proc refill_reflist {} { if {[commitinview $headids($n) $curview]} { lappend refs [list $n H] } else { - set commitinterest($headids($n)) {run refill_reflist} + interestedin $headids($n) {run refill_reflist} } } } @@ -7378,7 +8927,7 @@ proc refill_reflist {} { if {[commitinview $tagids($n) $curview]} { lappend refs [list $n T] } else { - set commitinterest($tagids($n)) {run refill_reflist} + interestedin $tagids($n) {run refill_reflist} } } } @@ -7387,7 +8936,7 @@ proc refill_reflist {} { if {[commitinview $otherrefids($n) $curview]} { lappend refs [list $n o] } else { - set commitinterest($otherrefids($n)) {run refill_reflist} + interestedin $otherrefids($n) {run refill_reflist} } } } @@ -8544,12 +10093,14 @@ proc rereadrefs {} { [array names idheads] [array names idotherrefs]]] foreach id $refids { set v [listrefs $id] - if {![info exists ref($id)] || $ref($id) != $v || - ($id eq $oldmainhead && $id ne $mainheadid) || - ($id eq $mainheadid && $id ne $oldmainhead)} { + if {![info exists ref($id)] || $ref($id) != $v} { redrawtags $id } } + if {$oldmainhead ne $mainheadid} { + redrawtags $oldmainhead + redrawtags $mainheadid + } run refill_reflist } @@ -8598,9 +10149,15 @@ proc showtag {tag isnew} { proc doquit {} { global stopped + global gitktmpdir + set stopped 100 savestuff . destroy . + + if {[info exists gitktmpdir]} { + catch {file delete -force $gitktmpdir} + } } proc mkfontdisp {font top which} { @@ -8616,6 +10173,7 @@ proc mkfontdisp {font top which} { proc choosefont {font which} { global fontparam fontlist fonttop fontattr + global prefstop set fontparam(which) $which set fontparam(font) $font @@ -8629,6 +10187,7 @@ proc choosefont {font which} { font create sample eval font config sample [font actual $font] toplevel $top + make_transient $top $prefstop wm title $top [mc "Gitk font chooser"] label $top.l -textvariable fontparam(which) pack $top.l -side top @@ -8662,6 +10221,8 @@ proc choosefont {font which} { frame $top.buts button $top.buts.ok -text [mc "OK"] -command fontok -default active button $top.buts.can -text [mc "Cancel"] -command fontcan -default normal + bind $top fontok + bind $top fontcan grid $top.buts.ok $top.buts.can grid columnconfigure $top.buts 0 -weight 1 -uniform a grid columnconfigure $top.buts 1 -weight 1 -uniform a @@ -8728,8 +10289,8 @@ proc chg_fontparam {v sub op} { proc doprefs {} { global maxwidth maxgraphpct global oldprefs prefstop showneartags showlocalchanges - global bgcolor fgcolor ctext diffcolors selectbgcolor - global tabstop limitdiffs + global bgcolor fgcolor ctext diffcolors selectbgcolor markbgcolor + global tabstop limitdiffs autoselect extdifftool perfile_attrs set top .gitkprefs set prefstop $top @@ -8738,11 +10299,12 @@ proc doprefs {} { return } foreach v {maxwidth maxgraphpct showneartags showlocalchanges \ - limitdiffs tabstop} { + limitdiffs tabstop perfile_attrs} { set oldprefs($v) [set $v] } toplevel $top wm title $top [mc "Gitk preferences"] + make_transient $top . label $top.ldisp -text [mc "Commit list display options"] grid $top.ldisp - -sticky w -pady 10 label $top.spacer -text " " @@ -8754,57 +10316,72 @@ proc doprefs {} { -font optionfont spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct grid x $top.maxpctl $top.maxpct -sticky w - frame $top.showlocal - label $top.showlocal.l -text [mc "Show local changes"] -font optionfont - checkbutton $top.showlocal.b -variable showlocalchanges - pack $top.showlocal.b $top.showlocal.l -side left + checkbutton $top.showlocal -text [mc "Show local changes"] \ + -font optionfont -variable showlocalchanges grid x $top.showlocal -sticky w + checkbutton $top.autoselect -text [mc "Auto-select SHA1"] \ + -font optionfont -variable autoselect + grid x $top.autoselect -sticky w label $top.ddisp -text [mc "Diff display options"] grid $top.ddisp - -sticky w -pady 10 label $top.tabstopl -text [mc "Tab spacing"] -font optionfont spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop grid x $top.tabstopl $top.tabstop -sticky w - frame $top.ntag - label $top.ntag.l -text [mc "Display nearby tags"] -font optionfont - checkbutton $top.ntag.b -variable showneartags - pack $top.ntag.b $top.ntag.l -side left + checkbutton $top.ntag -text [mc "Display nearby tags"] \ + -font optionfont -variable showneartags grid x $top.ntag -sticky w - frame $top.ldiff - label $top.ldiff.l -text [mc "Limit diffs to listed paths"] -font optionfont - checkbutton $top.ldiff.b -variable limitdiffs - pack $top.ldiff.b $top.ldiff.l -side left + checkbutton $top.ldiff -text [mc "Limit diffs to listed paths"] \ + -font optionfont -variable limitdiffs grid x $top.ldiff -sticky w + checkbutton $top.lattr -text [mc "Support per-file encodings"] \ + -font optionfont -variable perfile_attrs + grid x $top.lattr -sticky w + + entry $top.extdifft -textvariable extdifftool + frame $top.extdifff + label $top.extdifff.l -text [mc "External diff tool" ] -font optionfont \ + -padx 10 + button $top.extdifff.b -text [mc "Choose..."] -font optionfont \ + -command choose_extdiff + pack $top.extdifff.l $top.extdifff.b -side left + grid x $top.extdifff $top.extdifft -sticky w label $top.cdisp -text [mc "Colors: press to choose"] grid $top.cdisp - -sticky w -pady 10 label $top.bg -padx 40 -relief sunk -background $bgcolor button $top.bgbut -text [mc "Background"] -font optionfont \ - -command [list choosecolor bgcolor 0 $top.bg background setbg] + -command [list choosecolor bgcolor {} $top.bg [mc "background"] setbg] grid x $top.bgbut $top.bg -sticky w label $top.fg -padx 40 -relief sunk -background $fgcolor button $top.fgbut -text [mc "Foreground"] -font optionfont \ - -command [list choosecolor fgcolor 0 $top.fg foreground setfg] + -command [list choosecolor fgcolor {} $top.fg [mc "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 [mc "Diff: old lines"] -font optionfont \ - -command [list choosecolor diffcolors 0 $top.diffold "diff old lines" \ + -command [list choosecolor diffcolors 0 $top.diffold [mc "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 [mc "Diff: new lines"] -font optionfont \ - -command [list choosecolor diffcolors 1 $top.diffnew "diff new lines" \ - [list $ctext tag conf d1 -foreground]] + -command [list choosecolor diffcolors 1 $top.diffnew [mc "diff new lines"] \ + [list $ctext tag conf dresult -foreground]] grid x $top.diffnewbut $top.diffnew -sticky w label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2] button $top.hunksepbut -text [mc "Diff: hunk header"] -font optionfont \ -command [list choosecolor diffcolors 2 $top.hunksep \ - "diff hunk header" \ + [mc "diff hunk header"] \ [list $ctext tag conf hunksep -foreground]] grid x $top.hunksepbut $top.hunksep -sticky w + label $top.markbgsep -padx 40 -relief sunk -background $markbgcolor + button $top.markbgbut -text [mc "Marked line bg"] -font optionfont \ + -command [list choosecolor markbgcolor {} $top.markbgsep \ + [mc "marked line background"] \ + [list $ctext tag conf omark -background]] + grid x $top.markbgbut $top.markbgsep -sticky w label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor button $top.selbgbut -text [mc "Select bg"] -font optionfont \ - -command [list choosecolor selectbgcolor 0 $top.selbgsep background setselbg] + -command [list choosecolor selectbgcolor {} $top.selbgsep [mc "background"] setselbg] grid x $top.selbgbut $top.selbgsep -sticky w label $top.cfont -text [mc "Fonts: press to choose"] @@ -8816,6 +10393,8 @@ proc doprefs {} { frame $top.buts button $top.buts.ok -text [mc "OK"] -command prefsok -default active button $top.buts.can -text [mc "Cancel"] -command prefscan -default normal + bind $top prefsok + bind $top prefscan grid $top.buts.ok $top.buts.can grid columnconfigure $top.buts 0 -weight 1 -uniform a grid columnconfigure $top.buts 1 -weight 1 -uniform a @@ -8823,6 +10402,15 @@ proc doprefs {} { bind $top "focus $top.buts.ok" } +proc choose_extdiff {} { + global extdifftool + + set prog [tk_getOpenFile -title [mc "External diff tool"] -multiple false] + if {$prog ne {}} { + set extdifftool $prog + } +} + proc choosecolor {v vi w x cmd} { global $v @@ -8860,13 +10448,14 @@ proc setfg {c} { } allcanvs itemconf text -fill $c $canv itemconf circle -outline $c + $canv itemconf markid -outline $c } proc prefscan {} { global oldprefs prefstop foreach v {maxwidth maxgraphpct showneartags showlocalchanges \ - limitdiffs tabstop} { + limitdiffs tabstop perfile_attrs} { global $v set $v $oldprefs($v) } @@ -8879,7 +10468,7 @@ proc prefsok {} { global maxwidth maxgraphpct global oldprefs prefstop showneartags showlocalchanges global fontpref mainfont textfont uifont - global limitdiffs treediffs + global limitdiffs treediffs perfile_attrs catch {destroy $prefstop} unset prefstop @@ -8912,8 +10501,10 @@ proc prefsok {} { dohidelocalchanges } } - if {$limitdiffs != $oldprefs(limitdiffs)} { - # treediffs elements are limited by path + if {$limitdiffs != $oldprefs(limitdiffs) || + ($perfile_attrs && !$oldprefs(perfile_attrs))} { + # treediffs elements are limited by path; + # won't have encodings cached if perfile_attrs was just turned on catch {unset treediffs} } if {$fontchanged || $maxwidth != $oldprefs(maxwidth) @@ -9137,7 +10728,7 @@ set encoding_aliases { { ISO-8859-16 iso-ir-226 ISO_8859-16:2001 ISO_8859-16 latin10 l10 } { GBK CP936 MS936 windows-936 } { JIS_Encoding csJISEncoding } - { Shift_JIS MS_Kanji csShiftJIS } + { Shift_JIS MS_Kanji csShiftJIS ShiftJIS Shift-JIS } { Extended_UNIX_Code_Packed_Format_for_Japanese csEUCPkdFmtJapanese EUC-JP } { Extended_UNIX_Code_Fixed_Width_for_Japanese csEUCFixWidJapanese } @@ -9172,14 +10763,17 @@ set encoding_aliases { } proc tcl_encoding {enc} { - global encoding_aliases + global encoding_aliases tcl_encoding_cache + if {[info exists tcl_encoding_cache($enc)]} { + return $tcl_encoding_cache($enc) + } set names [encoding names] set lcnames [string tolower $names] set enc [string tolower $enc] set i [lsearch -exact $lcnames $enc] if {$i < 0} { # look for "isonnn" instead of "iso-nnn" or "iso_nnn" - if {[regsub {^iso[-_]} $enc iso encx]} { + if {[regsub {^(iso|cp|ibm|jis)[-_]} $enc {\1} encx]} { set i [lsearch -exact $lcnames $encx] } } @@ -9191,7 +10785,7 @@ proc tcl_encoding {enc} { foreach e $ll { set i [lsearch -exact $lcnames $e] if {$i < 0} { - if {[regsub {^iso[-_]} $e iso ex]} { + if {[regsub {^(iso|cp|ibm|jis)[-_]} $e {\1} ex]} { set i [lsearch -exact $lcnames $ex] } } @@ -9200,10 +10794,70 @@ proc tcl_encoding {enc} { break } } + set tclenc {} if {$i >= 0} { - return [lindex $names $i] + set tclenc [lindex $names $i] + } + set tcl_encoding_cache($enc) $tclenc + return $tclenc +} + +proc gitattr {path attr default} { + global path_attr_cache + if {[info exists path_attr_cache($attr,$path)]} { + set r $path_attr_cache($attr,$path) + } else { + set r "unspecified" + if {![catch {set line [exec git check-attr $attr -- $path]}]} { + regexp "(.*): encoding: (.*)" $line m f r + } + set path_attr_cache($attr,$path) $r + } + if {$r eq "unspecified"} { + return $default + } + return $r +} + +proc cache_gitattr {attr pathlist} { + global path_attr_cache + set newlist {} + foreach path $pathlist { + if {![info exists path_attr_cache($attr,$path)]} { + lappend newlist $path + } + } + set lim 1000 + if {[tk windowingsystem] == "win32"} { + # windows has a 32k limit on the arguments to a command... + set lim 30 + } + while {$newlist ne {}} { + set head [lrange $newlist 0 [expr {$lim - 1}]] + set newlist [lrange $newlist $lim end] + if {![catch {set rlist [eval exec git check-attr $attr -- $head]}]} { + foreach row [split $rlist "\n"] { + if {[regexp "(.*): encoding: (.*)" $row m path value]} { + if {[string index $path 0] eq "\""} { + set path [encoding convertfrom [lindex $path 0]] + } + set path_attr_cache($attr,$path) $value + } + } + } } - return {} +} + +proc get_path_encoding {path} { + global gui_encoding perfile_attrs + set tcl_enc $gui_encoding + if {$path ne {} && $perfile_attrs} { + set enc2 [tcl_encoding [gitattr $path encoding $tcl_enc]] + if {$enc2 ne {}} { + set tcl_enc $enc2 + } + } + return $tcl_enc } # First check that Tcl/Tk is recent enough @@ -9214,13 +10868,15 @@ if {[catch {package require Tk 8.4} err]} { } # defaults... -set datemode 0 set wrcomcmd "git diff-tree --stdin -p --pretty" set gitencoding {} catch { set gitencoding [exec git config --get i18n.commitencoding] } +catch { + set gitencoding [exec git config --get i18n.logoutputencoding] +} if {$gitencoding == ""} { set gitencoding "utf-8" } @@ -9229,9 +10885,28 @@ if {$tclencoding == {}} { puts stderr "Warning: encoding $gitencoding is not supported by Tcl/Tk" } -set mainfont {Helvetica 9} -set textfont {Courier 9} -set uifont {Helvetica 9 bold} +set gui_encoding [encoding system] +catch { + set enc [exec git config --get gui.encoding] + if {$enc ne {}} { + set tclenc [tcl_encoding $enc] + if {$tclenc ne {}} { + set gui_encoding $tclenc + } else { + puts stderr "Warning: encoding $enc is not supported by Tcl/Tk" + } + } +} + +if {[tk windowingsystem] eq "aqua"} { + set mainfont {{Lucida Grande} 9} + set textfont {Monaco 9} + set uifont {{Lucida Grande} 9 bold} +} else { + set mainfont {Helvetica 9} + set textfont {Courier 9} + set uifont {Helvetica 9 bold} +} set tabstop 8 set findmergefiles 0 set maxgraphpct 50 @@ -9249,6 +10924,14 @@ set maxlinelen 200 set showlocalchanges 1 set limitdiffs 1 set datetimeformat "%Y-%m-%d %H:%M:%S" +set autoselect 1 +set perfile_attrs 0 + +if {[tk windowingsystem] eq "aqua"} { + set extdifftool "opendiff" +} else { + set extdifftool "meld" +} set colors {green red blue magenta darkgrey brown orange} set bgcolor white @@ -9257,6 +10940,16 @@ set diffcolors {red "#00a000" blue} set diffcontext 3 set ignorespace 0 set selectbgcolor gray85 +set markbgcolor "#e0e0ff" + +set circlecolors {white blue gray blue blue} + +# button for popping up context menus +if {[tk windowingsystem] eq "aqua"} { + set ctxbut +} else { + set ctxbut +} ## For msgcat loading, first locate the installation location. if { [info exists ::env(GITK_MSGSDIR)] } { @@ -9304,22 +10997,26 @@ if {![file isdirectory $gitdir]} { exit 1 } -set mergeonly 0 +set selecthead {} +set selectheadid {} + set revtreeargs {} set cmdline_files {} set i 0 +set revtreeargscmd {} foreach arg $argv { - switch -- $arg { + switch -glob -- $arg { "" { } - "-d" { set datemode 1 } - "--merge" { - set mergeonly 1 - lappend revtreeargs $arg - } "--" { set cmdline_files [lrange $argv [expr {$i + 1}] end] break } + "--select-commit=*" { + set selecthead [string range $arg 16 end] + } + "--argscmd=*" { + set revtreeargscmd [string range $arg 10 end] + } default { lappend revtreeargs $arg } @@ -9327,8 +11024,12 @@ foreach arg $argv { incr i } +if {$selecthead eq "HEAD"} { + set selecthead {} +} + if {$i >= [llength $argv] && $revtreeargs ne {}} { - # no -- on command line, but some arguments (other than -d) + # no -- on command line, but some arguments (other than --argscmd) if {[catch { set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs] set cmdline_files [split $f "\n"] @@ -9356,42 +11057,9 @@ if {$i >= [llength $argv] && $revtreeargs ne {}} { } } -if {$mergeonly} { - # find the list of unmerged files - set mlist {} - set nr_unmerged 0 - if {[catch { - set fd [open "| git ls-files -u" r] - } err]} { - show_error {} . "[mc "Couldn't get list of unmerged files:"] $err" - exit 1 - } - while {[gets $fd line] >= 0} { - set i [string first "\t" $line] - if {$i < 0} continue - set fname [string range $line [expr {$i+1}] end] - if {[lsearch -exact $mlist $fname] >= 0} continue - incr nr_unmerged - if {$cmdline_files eq {} || [path_filter $cmdline_files $fname]} { - lappend mlist $fname - } - } - catch {close $fd} - if {$mlist eq {}} { - if {$nr_unmerged == 0} { - show_error {} . [mc "No files selected: --merge specified but\ - no files are unmerged."] - } else { - show_error {} . [mc "No files selected: --merge specified but\ - no unmerged files are within file limit."] - } - exit 1 - } - set cmdline_files $mlist -} - set nullid "0000000000000000000000000000000000000000" set nullid2 "0000000000000000000000000000000000000001" +set nullfile "/dev/null" set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}] @@ -9403,8 +11071,8 @@ set nhl_names {} set highlight_paths {} set findpattern {} set searchdirn -forwards -set boldrows {} -set boldnamerows {} +set boldids {} +set boldnameids {} set diffelide {0 0} set markingmatches 0 set linkentercount 0 @@ -9421,21 +11089,49 @@ set highlight_files {} set viewfiles(0) {} set viewperm(0) 0 set viewargs(0) {} +set viewargscmd(0) {} +set selectedline {} +set numcommits 0 set loginstance 0 set cmdlineok 0 set stopped 0 set stuffsaved 0 set patchnum 0 set lserial 0 +set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}] setcoords makewindow +catch { + image create photo gitlogo -width 16 -height 16 + + image create photo gitlogominus -width 4 -height 2 + gitlogominus put #C00000 -to 0 0 4 2 + gitlogo copy gitlogominus -to 1 5 + gitlogo copy gitlogominus -to 6 5 + gitlogo copy gitlogominus -to 11 5 + image delete gitlogominus + + image create photo gitlogoplus -width 4 -height 4 + gitlogoplus put #008000 -to 1 0 3 4 + gitlogoplus put #008000 -to 0 1 4 3 + gitlogo copy gitlogoplus -to 1 9 + gitlogo copy gitlogoplus -to 6 9 + gitlogo copy gitlogoplus -to 11 9 + image delete gitlogoplus + + image create photo gitlogo32 -width 32 -height 32 + gitlogo32 copy gitlogo -zoom 2 2 + + wm iconphoto . -default gitlogo gitlogo32 +} # wait for the window to become visible tkwait visibility . wm title . "[file tail $argv0]: [file tail [pwd]]" +update readrefs -if {$cmdline_files ne {} || $revtreeargs ne {}} { +if {$cmdline_files ne {} || $revtreeargs ne {} || $revtreeargscmd ne {}} { # create a view for the files/dirs specified on the command line set curview 1 set selectedview 1 @@ -9443,10 +11139,12 @@ if {$cmdline_files ne {} || $revtreeargs ne {}} { set viewname(1) [mc "Command line"] set viewfiles(1) $cmdline_files set viewargs(1) $revtreeargs + set viewargscmd(1) $revtreeargscmd set viewperm(1) 0 + set vdatemode(1) 0 addviewmenu 1 - .bar.view entryconf [mc "Edit view..."] -state normal - .bar.view entryconf [mc "Delete view"] -state normal + .bar.view entryconf [mca "Edit view..."] -state normal + .bar.view entryconf [mca "Delete view"] -state normal } if {[info exists permviews]} { @@ -9456,8 +11154,14 @@ if {[info exists permviews]} { set viewname($n) [lindex $v 0] set viewfiles($n) [lindex $v 1] set viewargs($n) [lindex $v 2] + set viewargscmd($n) [lindex $v 3] set viewperm($n) 1 addviewmenu $n } } -getcommits + +if {[tk windowingsystem] eq "win32"} { + focus -force . +} + +getcommits {}