git-format-patch --start-number <n>
[git.git] / gitk
diff --git a/gitk b/gitk
index a83a754..4aa57c0 100755 (executable)
--- a/gitk
+++ b/gitk
@@ -19,13 +19,13 @@ proc gitdir {} {
 proc start_rev_list {view} {
     global startmsecs nextupdate ncmupdate
     global commfd leftover tclencoding datemode
-    global revtreeargs viewfiles commitidx
+    global viewargs viewfiles commitidx
 
     set startmsecs [clock clicks -milliseconds]
     set nextupdate [expr {$startmsecs + 100}]
     set ncmupdate 1
     set commitidx($view) 0
-    set args $revtreeargs
+    set args $viewargs($view)
     if {$viewfiles($view) ne {}} {
        set args [concat $args "--" $viewfiles($view)]
     }
@@ -69,9 +69,7 @@ proc getcommits {} {
     set phase getcommits
     initlayout
     start_rev_list $curview
-    $canv delete all
-    $canv create text 3 3 -anchor nw -text "Reading commits..." \
-       -font $mainfont -tags textitems
+    show_status "Reading commits..."
 }
 
 proc getcommitlines {fd view}  {
@@ -84,26 +82,33 @@ proc getcommitlines {fd view}  {
     set stuff [read $fd]
     if {$stuff == {}} {
        if {![eof $fd]} return
+       global viewname
        unset commfd($view)
+       notbusy $view
        # set it blocking so we wait for the process to terminate
        fconfigure $fd -blocking 1
-       if {![catch {close $fd} err]} {
-           notbusy $view
-           if {$view == $curview} {
-               after idle finishcommits
+       if {[catch {close $fd} err]} {
+           set fv {}
+           if {$view != $curview} {
+               set fv " for the \"$viewname($view)\" view"
            }
-           return
+           if {[string range $err 0 4] == "usage"} {
+               set err "Gitk: error reading commits$fv:\
+                       bad arguments to git-rev-list."
+               if {$viewname($view) eq "Command line"} {
+                   append err \
+                       "  (Note: arguments to gitk are passed to git-rev-list\
+                        to allow selection of commits to be displayed.)"
+               }
+           } else {
+               set err "Error reading commits$fv: $err"
+           }
+           error_popup $err
        }
-       if {[string range $err 0 4] == "usage"} {
-           set err \
-               "Gitk: error reading commits: bad arguments to git-rev-list.\
-               (Note: arguments to gitk are passed to git-rev-list\
-               to allow selection of commits to be displayed.)"
-       } else {
-           set err "Error reading commits: $err"
+       if {$view == $curview} {
+           after idle finishcommits
        }
-       error_popup $err
-       exit 1
+       return
     }
     set start 0
     set gotsome 0
@@ -217,7 +222,7 @@ proc readcommit {id} {
 }
 
 proc updatecommits {} {
-    global viewdata curview revtreeargs phase displayorder
+    global viewdata curview phase displayorder
     global children commitrow
 
     if {$phase ne {}} {
@@ -352,10 +357,7 @@ proc readrefs {} {
     close $refd
 }
 
-proc error_popup msg {
-    set w .error
-    toplevel $w
-    wm transient $w .
+proc show_error {w msg} {
     message $w.m -text $msg -justify center -aspect 400
     pack $w.m -side top -fill x -padx 20 -pady 20
     button $w.ok -text OK -command "destroy $w"
@@ -365,6 +367,13 @@ proc error_popup msg {
     tkwait window $w
 }
 
+proc error_popup msg {
+    set w .error
+    toplevel $w
+    wm transient $w .
+    show_error $w $msg
+}
+
 proc makewindow {} {
     global canv canv2 canv3 linespc charspc ctext cflist
     global textfont mainfont uifont
@@ -512,7 +521,7 @@ proc makewindow {} {
     set ctext .ctop.cdet.left.ctext
     text $ctext -bg white -state disabled -font $textfont \
        -width $geometry(ctextw) -height $geometry(ctexth) \
-       -yscrollcommand scrolltext -wrap none
+       -yscrollcommand {.ctop.cdet.left.sb set} -wrap none
     scrollbar .ctop.cdet.left.sb -command "$ctext yview"
     pack .ctop.cdet.left.sb -side right -fill y
     pack $ctext -side left -fill both -expand 1
@@ -562,7 +571,8 @@ proc makewindow {} {
     scrollbar .ctop.cdet.right.sb -command "$cflist yview"
     pack .ctop.cdet.right.sb -side right -fill y
     pack $cflist -side left -fill both -expand 1
-    $cflist tag configure highlight -background yellow
+    $cflist tag configure highlight \
+       -background [$cflist cget -selectbackground]
     .ctop.cdet add .ctop.cdet.right
     bind .ctop.cdet <Configure> {resizecdetpanes %W %w}
 
@@ -685,7 +695,7 @@ proc savestuff {w} {
     global canv canv2 canv3 ctext cflist mainfont textfont uifont
     global stuffsaved findmergefiles maxgraphpct
     global maxwidth
-    global viewname viewfiles viewperm nextviewnum
+    global viewname viewfiles viewargs viewperm nextviewnum
     global cmitmode
 
     if {$stuffsaved} return
@@ -714,7 +724,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)]}"
+               puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v)]}"
            }
        }
        puts $f "}"
@@ -1092,14 +1102,13 @@ image create bitmap tri-dn -background black -foreground blue -data {
 }
 
 proc init_flist {first} {
-    global cflist cflist_top cflist_bot selectedline difffilestart
+    global cflist cflist_top selectedline difffilestart
 
     $cflist conf -state normal
     $cflist delete 0.0 end
     if {$first ne {}} {
        $cflist insert end $first
        set cflist_top 1
-       set cflist_bot 1
        $cflist tag add highlight 1.0 "1.0 lineend"
     } else {
        catch {unset cflist_top}
@@ -1126,67 +1135,115 @@ proc sel_flist {w x y} {
     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}]]}
     }
-    highlight_flist $l
 }
 
-proc scrolltext {f0 f1} {
-    global cflist_top
+# Functions for adding and removing shell-type quoting
 
-    .ctop.cdet.left.sb set $f0 $f1
-    if {[info exists cflist_top]} {
-       highlight_flist $cflist_top
+proc shellquote {str} {
+    if {![string match "*\['\"\\ \t]*" $str]} {
+       return $str
     }
-}
-
-# Given an index $tl in the $ctext window, this works out which line
-# of the $cflist window displays the filename whose patch is shown
-# at the given point in the $ctext window.  $ll is a hint about which
-# line it might be, and is used as the starting point of the search.
-proc ctext_index {tl ll} {
-    global ctext difffilestart
-
-    while {$ll >= 2 && [$ctext compare $tl < \
-                           [lindex $difffilestart [expr {$ll - 2}]]]} {
-       incr ll -1
+    if {![string match "*\['\"\\]*" $str]} {
+       return "\"$str\""
     }
-    set nfiles [llength $difffilestart]
-    while {$ll - 1 < $nfiles && [$ctext compare $tl >= \
-                           [lindex $difffilestart [expr {$ll - 1}]]]} {
-       incr ll
+    if {![string match "*'*" $str]} {
+       return "'$str'"
     }
-    return $ll
+    return "\"[string map {\" \\\" \\ \\\\} $str]\""
 }
 
-proc highlight_flist {ll} {
-    global ctext cflist cflist_top cflist_bot difffilestart
-
-    if {![info exists difffilestart] || [llength $difffilestart] == 0} return
-    set ll [ctext_index [$ctext index @0,1] $ll]
-    set lb $cflist_bot
-    if {$lb < $ll} {
-       set lb $ll
+proc shellarglist {l} {
+    set str {}
+    foreach a $l {
+       if {$str ne {}} {
+           append str " "
+       }
+       append str [shellquote $a]
     }
-    set y [expr {[winfo height $ctext] - 2}]
-    set lb [ctext_index [$ctext index @0,$y] $lb]
-    if {$ll != $cflist_top || $lb != $cflist_bot} {
-       $cflist tag remove highlight $cflist_top.0 "$cflist_bot.0 lineend"
-       for {set l $ll} {$l <= $lb} {incr l} {
-           $cflist tag add highlight $l.0 "$l.0 lineend"
+    return $str
+}
+
+proc shelldequote {str} {
+    set ret {}
+    set used -1
+    while {1} {
+       incr used
+       if {![regexp -start $used -indices "\['\"\\\\ \t]" $str first]} {
+           append ret [string range $str $used end]
+           set used [string length $str]
+           break
+       }
+       set first [lindex $first 0]
+       set ch [string index $str $first]
+       if {$first > $used} {
+           append ret [string range $str $used [expr {$first - 1}]]
+           set used $first
+       }
+       if {$ch eq " " || $ch eq "\t"} break
+       incr used
+       if {$ch eq "'"} {
+           set first [string first "'" $str $used]
+           if {$first < 0} {
+               error "unmatched single-quote"
+           }
+           append ret [string range $str $used [expr {$first - 1}]]
+           set used $first
+           continue
        }
-       set cflist_top $ll
-       set cflist_bot $lb
+       if {$ch eq "\\"} {
+           if {$used >= [string length $str]} {
+               error "trailing backslash"
+           }
+           append ret [string index $str $used]
+           continue
+       }
+       # here ch == "\""
+       while {1} {
+           if {![regexp -start $used -indices "\[\"\\\\]" $str first]} {
+               error "unmatched double-quote"
+           }
+           set first [lindex $first 0]
+           set ch [string index $str $first]
+           if {$first > $used} {
+               append ret [string range $str $used [expr {$first - 1}]]
+               set used $first
+           }
+           if {$ch eq "\""} break
+           incr used
+           append ret [string index $str $used]
+           incr used
+       }
+    }
+    return [list $used $ret]
+}
+
+proc shellsplit {str} {
+    set l {}
+    while {1} {
+       set str [string trimleft $str]
+       if {$str eq {}} break
+       set dq [shelldequote $str]
+       set n [lindex $dq 0]
+       set word [lindex $dq 1]
+       set str [string range $str $n end]
+       lappend l $word
     }
+    return $l
 }
 
 # Code to implement multiple views
 
 proc newview {ishighlight} {
     global nextviewnum newviewname newviewperm uifont newishighlight
+    global newviewargs revtreeargs
 
     set newishighlight $ishighlight
     set top .gitkview
@@ -1196,12 +1253,14 @@ proc newview {ishighlight} {
     }
     set newviewname($nextviewnum) "View $nextviewnum"
     set newviewperm($nextviewnum) 0
+    set newviewargs($nextviewnum) [shellarglist $revtreeargs]
     vieweditor $top $nextviewnum "Gitk view definition" 
 }
 
 proc editview {} {
     global curview
     global viewname viewperm newviewname newviewperm
+    global viewargs newviewargs
 
     set top .gitkvedit-$curview
     if {[winfo exists $top]} {
@@ -1210,6 +1269,7 @@ proc editview {} {
     }
     set newviewname($curview) $viewname($curview)
     set newviewperm($curview) $viewperm($curview)
+    set newviewargs($curview) [shellarglist $viewargs($curview)]
     vieweditor $top $curview "Gitk: edit view $viewname($curview)"
 }
 
@@ -1224,7 +1284,13 @@ proc vieweditor {top n title} {
     grid $top.nl $top.name -sticky w -pady 5
     checkbutton $top.perm -text "Remember this view" -variable newviewperm($n)
     grid $top.perm - -pady 5 -sticky w
-    message $top.l -aspect 500 -font $uifont \
+    message $top.al -aspect 1000 -font $uifont \
+       -text "Commits to include (arguments to git-rev-list):"
+    grid $top.al - -sticky w -pady 5
+    entry $top.args -width 50 -textvariable newviewargs($n) \
+       -background white
+    grid $top.args - -sticky ew -padx 5
+    message $top.l -aspect 1000 -font $uifont \
        -text "Enter files and directories to include, one per line:"
     grid $top.l - -sticky w
     text $top.t -width 40 -height 10 -background white
@@ -1236,7 +1302,7 @@ proc vieweditor {top n title} {
        $top.t delete {end - 1c} end
        $top.t mark set insert 0.0
     }
-    grid $top.t - -sticky w -padx 5
+    grid $top.t - -sticky ew -padx 5
     frame $top.buts
     button $top.buts.ok -text "OK" -command [list newviewok $top $n]
     button $top.buts.can -text "Cancel" -command [list destroy $top]
@@ -1258,14 +1324,23 @@ proc doviewmenu {m first cmd op args} {
 }
 
 proc allviewmenus {n op args} {
-    doviewmenu .bar.view 6 [list showview $n] $op $args
+    doviewmenu .bar.view 7 [list showview $n] $op $args
     doviewmenu .bar.view.hl 3 [list addhighlight $n] $op $args
 }
 
 proc newviewok {top n} {
     global nextviewnum newviewperm newviewname newishighlight
     global viewname viewfiles viewperm selectedview curview
+    global viewargs newviewargs
 
+    if {[catch {
+       set newargs [shellsplit $newviewargs($n)]
+    } err]} {
+       error_popup "Error in commit selection arguments: $err"
+       wm raise $top
+       focus $top
+       return
+    }
     set files {}
     foreach f [split [$top.t get 0.0 end] "\n"] {
        set ft [string trim $f]
@@ -1279,6 +1354,7 @@ proc newviewok {top n} {
        set viewname($n) $newviewname($n)
        set viewperm($n) $newviewperm($n)
        set viewfiles($n) $files
+       set viewargs($n) $newargs
        addviewmenu $n
        if {!$newishighlight} {
            after idle showview $n
@@ -1292,8 +1368,9 @@ proc newviewok {top n} {
            set viewname($n) $newviewname($n)
            allviewmenus $n entryconf -label $viewname($n)
        }
-       if {$files ne $viewfiles($n)} {
+       if {$files ne $viewfiles($n) || $newargs ne $viewargs($n)} {
            set viewfiles($n) $files
+           set viewargs($n) $newargs
            if {$curview == $n} {
                after idle updatecommits
            }
@@ -1394,8 +1471,8 @@ proc showview {n} {
     set curview $n
     set selectedview $n
     set selectedhlview -1
-    .bar.view entryconf 1 -state [expr {$n == 0? "disabled": "normal"}]
     .bar.view entryconf 2 -state [expr {$n == 0? "disabled": "normal"}]
+    .bar.view entryconf 3 -state [expr {$n == 0? "disabled": "normal"}]
     catch {unset hlview}
     .bar.view.hl entryconf 1 -state disabled
 
@@ -1450,15 +1527,15 @@ proc showview {n} {
     selectline $row 0
     if {$phase ne {}} {
        if {$phase eq "getcommits"} {
-           global mainfont
-           $canv create text 3 3 -anchor nw -text "Reading commits..." \
-               -font $mainfont -tags textitems
+           show_status "Reading commits..."
        }
        if {[info exists commfd($n)]} {
            layoutmore
        } else {
            finishcommits
        }
+    } elseif {$numcommits == 0} {
+       show_status "No commits selected"
     }
 }
 
@@ -2580,6 +2657,13 @@ proc xcoord {i level ln} {
     return $x
 }
 
+proc show_status {msg} {
+    global canv mainfont
+
+    clear_display
+    $canv create text 3 3 -anchor nw -text $msg -font $mainfont -tags textitems
+}
+
 proc finishcommits {} {
     global commitidx phase curview
     global canv mainfont ctext maincursor textcursor
@@ -2588,9 +2672,7 @@ proc finishcommits {} {
     if {$commitidx($curview) > 0} {
        drawrest
     } else {
-       $canv delete all
-       $canv create text 3 3 -anchor nw -text "No commits selected" \
-           -font $mainfont -tags textitems
+       show_status "No commits selected"
     }
     set phase {}
     catch {unset pending_select}
@@ -3561,8 +3643,6 @@ proc getmergediffline {mdf id np} {
        # start of a new file
        $ctext insert end "\n"
        set here [$ctext index "end - 1c"]
-       $ctext mark set f:$fname $here
-       $ctext mark gravity f:$fname left
        lappend difffilestart $here
        add_flist [list $fname]
        set l [expr {(78 - [string length $fname]) / 2}]
@@ -3693,6 +3773,19 @@ proc getblobdiffs {ids} {
     set nextupdate [expr {[clock clicks -milliseconds] + 100}]
 }
 
+proc setinlist {var i val} {
+    global $var
+
+    while {[llength [set $var]] < $i} {
+       lappend $var {}
+    }
+    if {[llength [set $var]] == $i} {
+       lappend $var $val
+    } else {
+       lset $var $i $val
+    }
+}
+
 proc getblobdiffline {bdf ids} {
     global diffids blobdifffd ctext curdifftag curtagstart
     global diffnexthead diffnextnote difffilestart
@@ -3719,12 +3812,15 @@ proc getblobdiffline {bdf ids} {
        set here [$ctext index "end - 1c"]
        set curtagstart $here
        set header $newname
-       lappend difffilestart $here
-       $ctext mark set f:$fname $here
-       $ctext mark gravity f:$fname left
-       if {$newname != $fname} {
-           $ctext mark set f:$newfname $here
-           $ctext mark gravity f:$newfname left
+       set i [lsearch -exact $treediffs($ids) $fname]
+       if {$i >= 0} {
+           setinlist difffilestart $i $here
+       }
+       if {$newname ne $fname} {
+           set i [lsearch -exact $treediffs($ids) $newname]
+           if {$i >= 0} {
+               setinlist difffilestart $i $here
+           }
        }
        set curdifftag "f:$fname"
        $ctext tag delete $curdifftag
@@ -4815,10 +4911,33 @@ foreach arg $argv {
 # check that we can find a .git directory somewhere...
 set gitdir [gitdir]
 if {![file isdirectory $gitdir]} {
-    error_popup "Cannot find the git directory \"$gitdir\"."
+    show_error . "Cannot find the git directory \"$gitdir\"."
     exit 1
 }
 
+set cmdline_files {}
+set i [lsearch -exact $revtreeargs "--"]
+if {$i >= 0} {
+    set cmdline_files [lrange $revtreeargs [expr {$i + 1}] end]
+    set revtreeargs [lrange $revtreeargs 0 [expr {$i - 1}]]
+} elseif {$revtreeargs ne {}} {
+    if {[catch {
+       set f [eval exec git-rev-parse --no-revs --no-flags $revtreeargs]
+       set cmdline_files [split $f "\n"]
+       set n [llength $cmdline_files]
+       set revtreeargs [lrange $revtreeargs 0 end-$n]
+    } err]} {
+       # unfortunately we get both stdout and stderr in $err,
+       # so look for "fatal:".
+       set i [string first "fatal:" $err]
+       if {$i > 0} {
+           set err [string range [expr {$i + 6}] end]
+       }
+       show_error . "Bad arguments to gitk:\n$err"
+       exit 1
+    }
+}
+
 set history {}
 set historyindex 0
 
@@ -4830,7 +4949,9 @@ set selectedview 0
 set selectedhlview {}
 set viewfiles(0) {}
 set viewperm(0) 0
+set viewargs(0) {}
 
+set cmdlineok 0
 set stopped 0
 set stuffsaved 0
 set patchnum 0
@@ -4838,28 +4959,18 @@ setcoords
 makewindow
 readrefs
 
-set cmdline_files {}
-catch {
-    set fileargs [eval exec git-rev-parse --no-revs --no-flags $revtreeargs]
-    set cmdline_files [split $fileargs "\n"]
-    set n [llength $cmdline_files]
-    set revtreeargs [lrange $revtreeargs 0 end-$n]
-}
-if {[lindex $revtreeargs end] eq "--"} {
-    set revtreeargs [lrange $revtreeargs 0 end-1]
-}
-
-if {$cmdline_files ne {}} {
+if {$cmdline_files ne {} || $revtreeargs ne {}} {
     # create a view for the files/dirs specified on the command line
     set curview 1
     set selectedview 1
     set nextviewnum 2
     set viewname(1) "Command line"
     set viewfiles(1) $cmdline_files
+    set viewargs(1) $revtreeargs
     set viewperm(1) 0
     addviewmenu 1
-    .bar.view entryconf 1 -state normal
     .bar.view entryconf 2 -state normal
+    .bar.view entryconf 3 -state normal
 }
 
 if {[info exists permviews]} {
@@ -4868,6 +4979,7 @@ if {[info exists permviews]} {
        incr nextviewnum
        set viewname($n) [lindex $v 0]
        set viewfiles($n) [lindex $v 1]
+       set viewargs($n) [lindex $v 2]
        set viewperm($n) 1
        addviewmenu $n
     }