git-fetch-pack: Support multi_ack extension
[git.git] / gitk
diff --git a/gitk b/gitk
index 33abcc4..a9d37d9 100755 (executable)
--- a/gitk
+++ b/gitk
@@ -1,6 +1,6 @@
 #!/bin/sh
 # Tcl ignores the next line -*- tcl -*- \
-exec wish "$0" -- "${1+$@}"
+exec wish "$0" -- "$@"
 
 # Copyright (C) 2005 Paul Mackerras.  All rights reserved.
 # This program is free software; it may be used, copied, modified
@@ -238,7 +238,8 @@ proc parsecommit {id contents listed olds} {
 }
 
 proc readrefs {} {
-    global tagids idtags headids idheads
+    global tagids idtags headids idheads tagcontents
+
     set tags [glob -nocomplain -types f [gitdir]/refs/tags/*]
     foreach f $tags {
        catch {
@@ -248,7 +249,8 @@ proc readrefs {} {
                set direct [file tail $f]
                set tagids($direct) $id
                lappend idtags($id) $direct
-               set contents [split [exec git-cat-file tag $id] "\n"]
+               set tagblob [exec git-cat-file tag $id]
+               set contents [split $tagblob "\n"]
                set obj {}
                set type {}
                set tag {}
@@ -263,6 +265,7 @@ proc readrefs {} {
                if {$obj != {} && $type == "commit" && $tag != {}} {
                    set tagids($tag) $obj
                    lappend idtags($obj) $tag
+                   set tagcontents($tag) $tagblob
                }
            }
            close $fd
@@ -281,6 +284,32 @@ proc readrefs {} {
            close $fd
        }
     }
+    readotherrefs refs {} {tags heads}
+}
+
+proc readotherrefs {base dname excl} {
+    global otherrefids idotherrefs
+
+    set git [gitdir]
+    set files [glob -nocomplain -types f [file join $git $base *]]
+    foreach f $files {
+       catch {
+           set fd [open $f r]
+           set line [read $fd 40]
+           if {[regexp {^[0-9a-f]{40}} $line id]} {
+               set name "$dname[file tail $f]"
+               set otherrefids($name) $id
+               lappend idotherrefs($id) $name
+           }
+           close $fd
+       }
+    }
+    set dirs [glob -nocomplain -types d [file join $git $base *]]
+    foreach d $dirs {
+       set dir [file tail $d]
+       if {[lsearch -exact $excl $dir] >= 0} continue
+       readotherrefs [file join $base $dir] "$dname$dir/" {}
+    }
 }
 
 proc error_popup msg {
@@ -305,6 +334,7 @@ proc makewindow {} {
     menu .bar
     .bar add cascade -label "File" -menu .bar.file
     menu .bar.file
+    .bar.file add command -label "Reread references" -command rereadrefs
     .bar.file add command -label "Quit" -command doquit
     menu .bar.help
     .bar add cascade -label "Help" -menu .bar.help
@@ -456,6 +486,8 @@ proc makewindow {} {
     bindall <B2-Motion> "allcanvs scan dragto 0 %y"
     bind . <Key-Up> "selnextline -1"
     bind . <Key-Down> "selnextline 1"
+    bind . <Key-Right> "goforw"
+    bind . <Key-Left> "goback"
     bind . <Key-Prior> "allcanvs yview scroll -1 pages"
     bind . <Key-Next> "allcanvs yview scroll 1 pages"
     bindkey <Key-Delete> "$ctext yview scroll -1 pages"
@@ -463,6 +495,12 @@ proc makewindow {} {
     bindkey <Key-space> "$ctext yview scroll 1 pages"
     bindkey p "selnextline -1"
     bindkey n "selnextline 1"
+    bindkey z "goback"
+    bindkey x "goforw"
+    bindkey i "selnextline -1"
+    bindkey k "selnextline 1"
+    bindkey j "goback"
+    bindkey l "goforw"
     bindkey b "$ctext yview scroll -1 pages"
     bindkey d "$ctext yview scroll 18 units"
     bindkey u "$ctext yview scroll -18 units"
@@ -528,6 +566,7 @@ proc click {w} {
 proc savestuff {w} {
     global canv canv2 canv3 ctext cflist mainfont textfont
     global stuffsaved findmergefiles gaudydiff maxgraphpct
+    global maxwidth
 
     if {$stuffsaved} return
     if {![winfo viewable .]} return
@@ -538,6 +577,7 @@ proc savestuff {w} {
        puts $f [list set findmergefiles $findmergefiles]
        puts $f [list set gaudydiff $gaudydiff]
        puts $f [list set maxgraphpct $maxgraphpct]
+       puts $f [list set maxwidth $maxwidth]
        puts $f "set geometry(width) [winfo width .ctop]"
        puts $f "set geometry(height) [winfo height .ctop]"
        puts $f "set geometry(canv1) [expr [winfo width $canv]-2]"
@@ -743,13 +783,39 @@ proc bindline {t id} {
     $canv bind $t <Button-1> "lineclick %x %y $id 1"
 }
 
+proc drawlines {id xtra} {
+    global mainline mainlinearrow sidelines lthickness colormap canv
+
+    $canv delete lines.$id
+    if {[info exists mainline($id)]} {
+       set t [$canv create line $mainline($id) \
+                  -width [expr {($xtra + 1) * $lthickness}] \
+                  -fill $colormap($id) -tags lines.$id \
+                  -arrow $mainlinearrow($id)]
+       $canv lower $t
+       bindline $t $id
+    }
+    if {[info exists sidelines($id)]} {
+       foreach ls $sidelines($id) {
+           set coords [lindex $ls 0]
+           set thick [lindex $ls 1]
+           set arrow [lindex $ls 2]
+           set t [$canv create line $coords -fill $colormap($id) \
+                      -width [expr {($thick + $xtra) * $lthickness}] \
+                      -arrow $arrow -tags lines.$id]
+           $canv lower $t
+           bindline $t $id
+       }
+    }
+}
+
 # level here is an index in displist
 proc drawcommitline {level} {
     global parents children nparents displist
     global canv canv2 canv3 mainfont namefont canvy linespc
     global lineid linehtag linentag linedtag commitinfo
     global colormap numcommits currentparents dupparents
-    global idtags idline idheads
+    global idtags idline idheads idotherrefs
     global lineno lthickness mainline mainlinearrow sidelines
     global commitlisted rowtextx idpos lastuse displist
     global oldnlines olddlevel olddisplist
@@ -791,23 +857,8 @@ proc drawcommitline {level} {
        if {$mainlinearrow($id) ne "none"} {
            set mainline($id) [trimdiagstart $mainline($id)]
        }
-       set t [$canv create line $mainline($id) \
-                  -width $lthickness -fill $colormap($id) \
-                  -arrow $mainlinearrow($id)]
-       $canv lower $t
-       bindline $t $id
-    }
-    if {[info exists sidelines($id)]} {
-       foreach ls $sidelines($id) {
-           set coords [lindex $ls 0]
-           set thick [lindex $ls 1]
-           set arrow [lindex $ls 2]
-           set t [$canv create line $coords -fill $colormap($id) \
-                      -width [expr {$thick * $lthickness}] -arrow $arrow]
-           $canv lower $t
-           bindline $t $id
-       }
     }
+    drawlines $id 0
     set orad [expr {$linespc / 3}]
     set t [$canv create oval [expr $x - $orad] [expr $y1 - $orad] \
               [expr $x + $orad - 1] [expr $y1 + $orad - 1] \
@@ -820,7 +871,8 @@ proc drawcommitline {level} {
     }
     set rowtextx($lineno) $xt
     set idpos($id) [list $x $xt $y1]
-    if {[info exists idtags($id)] || [info exists idheads($id)]} {
+    if {[info exists idtags($id)] || [info exists idheads($id)]
+       || [info exists idotherrefs($id)]} {
        set xt [drawtags $id $x $xt $y1]
     }
     set headline [lindex $commitinfo($id) 0]
@@ -840,18 +892,23 @@ proc drawcommitline {level} {
 }
 
 proc drawtags {id x xt y1} {
-    global idtags idheads
+    global idtags idheads idotherrefs
     global linespc lthickness
-    global canv mainfont
+    global canv mainfont idline rowtextx
 
     set marks {}
     set ntags 0
+    set nheads 0
     if {[info exists idtags($id)]} {
        set marks $idtags($id)
        set ntags [llength $marks]
     }
     if {[info exists idheads($id)]} {
        set marks [concat $marks $idheads($id)]
+       set nheads [llength $idheads($id)]
+    }
+    if {[info exists idotherrefs($id)]} {
+       set marks [concat $marks $idotherrefs($id)]
     }
     if {$marks eq {}} {
        return $xt
@@ -876,17 +933,27 @@ proc drawtags {id x xt y1} {
        set xr [expr $x + $delta + $wid + $lthickness]
        if {[incr ntags -1] >= 0} {
            # draw a tag
-           $canv create polygon $x [expr $yt + $delta] $xl $yt\
-               $xr $yt $xr $yb $xl $yb $x [expr $yb - $delta] \
-               -width 1 -outline black -fill yellow -tags tag.$id
+           set t [$canv create polygon $x [expr $yt + $delta] $xl $yt \
+                      $xr $yt $xr $yb $xl $yb $x [expr $yb - $delta] \
+                      -width 1 -outline black -fill yellow -tags tag.$id]
+           $canv bind $t <1> [list showtag $tag 1]
+           set rowtextx($idline($id)) [expr {$xr + $linespc}]
        } else {
-           # draw a head
+           # draw a head or other ref
+           if {[incr nheads -1] >= 0} {
+               set col green
+           } else {
+               set col "#ddddff"
+           }
            set xl [expr $xl - $delta/2]
            $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
-               -width 1 -outline black -fill green -tags tag.$id
+               -width 1 -outline black -fill $col -tags tag.$id
+       }
+       set t [$canv create text $xl $y1 -anchor w -text $tag \
+                  -font $mainfont -tags tag.$id]
+       if {$ntags >= 0} {
+           $canv bind $t <1> [list showtag $tag 1]
        }
-       $canv create text $xl $y1 -anchor w -text $tag \
-           -font $mainfont -tags tag.$id
     }
     return $xt
 }
@@ -1019,6 +1086,7 @@ proc drawslants {id needonscreen nohs} {
     }
     if {$onscreen($id) == 0} {
        lappend displist $id
+       set onscreen($id) 1
     }
 
     # remove the null entry if present
@@ -1186,15 +1254,10 @@ proc drawslants {id needonscreen nohs} {
                set j [lsearch -exact $displist $id]
            }
            if {$j != $i || $xspc1($lineno) != $xspc1($lj)
-               || ($olddlevel <= $i && $i <= $dlevel)
-               || ($dlevel <= $i && $i <= $olddlevel)} {
+               || ($olddlevel < $i && $i < $dlevel)
+               || ($dlevel < $i && $i < $olddlevel)} {
                set xj [xcoord $j $dlevel $lj]
-               set dx [expr {abs($xi - $xj)}]
-               set yb $y2
-               if {0 && $dx < $linespc} {
-                   set yb [expr {$y1 + $dx}]
-               }
-               lappend mainline($id) $xi $y1 $xj $yb
+               lappend mainline($id) $xi $y1 $xj $y2
            }
        }
     }
@@ -1460,7 +1523,7 @@ proc drawrest {} {
     global phase stopped redisplaying selectedline
     global datemode todo displayorder
     global numcommits ncmupdate
-    global nextupdate startmsecs idline
+    global nextupdate startmsecs
 
     set level [decidenext]
     if {$level >= 0} {
@@ -1982,14 +2045,40 @@ proc commit_descriptor {p} {
     return "$p ($l)"
 }
 
+# append some text to the ctext widget, and make any SHA1 ID
+# that we know about be a clickable link.
+proc appendwithlinks {text} {
+    global ctext idline linknum
+
+    set start [$ctext index "end - 1c"]
+    $ctext insert end $text
+    $ctext insert end "\n"
+    set links [regexp -indices -all -inline {[0-9a-f]{40}} $text]
+    foreach l $links {
+       set s [lindex $l 0]
+       set e [lindex $l 1]
+       set linkid [string range $text $s $e]
+       if {![info exists idline($linkid)]} continue
+       incr e
+       $ctext tag add link "$start + $s c" "$start + $e c"
+       $ctext tag add link$linknum "$start + $s c" "$start + $e c"
+       $ctext tag bind link$linknum <1> [list selectline $idline($linkid) 1]
+       incr linknum
+    }
+    $ctext tag conf link -foreground blue -underline 1
+    $ctext tag bind link <Enter> { %W configure -cursor hand2 }
+    $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
+}
+
 proc selectline {l isnew} {
     global canv canv2 canv3 ctext commitinfo selectedline
     global lineid linehtag linentag linedtag
     global canvy0 linespc parents nparents children
     global cflist currentid sha1entry
-    global commentend idtags idline
+    global commentend idtags idline linknum
 
     $canv delete hover
+    normalline
     if {![info exists lineid($l)] || ![info exists linehtag($l)]} return
     $canv delete secsel
     set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
@@ -2053,6 +2142,7 @@ proc selectline {l isnew} {
 
     $ctext conf -state normal
     $ctext delete 0.0 end
+    set linknum 0
     $ctext mark set fmark.0 0.0
     $ctext mark gravity fmark.0 left
     set info $commitinfo($id)
@@ -2066,7 +2156,6 @@ proc selectline {l isnew} {
        $ctext insert end "\n"
     }
  
-    set commentstart [$ctext index "end - 1c"]
     set comment {}
     if {[info exists parents($id)]} {
        foreach p $parents($id) {
@@ -2080,26 +2169,9 @@ proc selectline {l isnew} {
     }
     append comment "\n"
     append comment [lindex $info 5]
-    $ctext insert end $comment
-    $ctext insert end "\n"
 
     # make anything that looks like a SHA1 ID be a clickable link
-    set links [regexp -indices -all -inline {[0-9a-f]{40}} $comment]
-    set i 0
-    foreach l $links {
-       set s [lindex $l 0]
-       set e [lindex $l 1]
-       set linkid [string range $comment $s $e]
-       if {![info exists idline($linkid)]} continue
-       incr e
-       $ctext tag add link "$commentstart + $s c" "$commentstart + $e c"
-       $ctext tag add link$i "$commentstart + $s c" "$commentstart + $e c"
-       $ctext tag bind link$i <1> [list selectline $idline($linkid) 1]
-       incr i
-    }
-    $ctext tag conf link -foreground blue -underline 1
-    $ctext tag bind link <Enter> { %W configure -cursor hand2 }
-    $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
+    appendwithlinks $comment
 
     $ctext tag delete Comments
     $ctext tag remove found 1.0 end
@@ -2734,7 +2806,7 @@ proc gettreediffs {ids} {
     set treediff {}
     set id [lindex $ids 0]
     set p [lindex $ids 1]
-    if [catch {set gdtf [open "|git-diff-tree -r $p $id" r]}] return
+    if [catch {set gdtf [open "|git-diff-tree -r $id" r]}] return
     fconfigure $gdtf -blocking 0
     fileevent $gdtf readable [list gettreediffline $gdtf $ids]
 }
@@ -2770,7 +2842,7 @@ proc getblobdiffs {ids} {
     set id [lindex $ids 0]
     set p [lindex $ids 1]
     set env(GIT_DIFF_OPTS) $diffopts
-    set cmd [list | git-diff-tree -r -p -C $p $id]
+    set cmd [list | git-diff-tree -r -p -C $id]
     if {[catch {set bdf [open $cmd r]} err]} {
        puts "error getting diffs: $err"
        return
@@ -3075,15 +3147,102 @@ proc linehover {} {
     $canv raise $t
 }
 
+proc clickisonarrow {id y} {
+    global mainline mainlinearrow sidelines lthickness
+
+    set thresh [expr {2 * $lthickness + 6}]
+    if {[info exists mainline($id)]} {
+       if {$mainlinearrow($id) ne "none"} {
+           if {abs([lindex $mainline($id) 1] - $y) < $thresh} {
+               return "up"
+           }
+       }
+    }
+    if {[info exists sidelines($id)]} {
+       foreach ls $sidelines($id) {
+           set coords [lindex $ls 0]
+           set arrow [lindex $ls 2]
+           if {$arrow eq "first" || $arrow eq "both"} {
+               if {abs([lindex $coords 1] - $y) < $thresh} {
+                   return "up"
+               }
+           }
+           if {$arrow eq "last" || $arrow eq "both"} {
+               if {abs([lindex $coords end] - $y) < $thresh} {
+                   return "down"
+               }
+           }
+       }
+    }
+    return {}
+}
+
+proc arrowjump {id dirn y} {
+    global mainline sidelines canv
+
+    set yt {}
+    if {$dirn eq "down"} {
+       if {[info exists mainline($id)]} {
+           set y1 [lindex $mainline($id) 1]
+           if {$y1 > $y} {
+               set yt $y1
+           }
+       }
+       if {[info exists sidelines($id)]} {
+           foreach ls $sidelines($id) {
+               set y1 [lindex $ls 0 1]
+               if {$y1 > $y && ($yt eq {} || $y1 < $yt)} {
+                   set yt $y1
+               }
+           }
+       }
+    } else {
+       if {[info exists sidelines($id)]} {
+           foreach ls $sidelines($id) {
+               set y1 [lindex $ls 0 end]
+               if {$y1 < $y && ($yt eq {} || $y1 > $yt)} {
+                   set yt $y1
+               }
+           }
+       }
+    }
+    if {$yt eq {}} return
+    set ymax [lindex [$canv cget -scrollregion] 3]
+    if {$ymax eq {} || $ymax <= 0} return
+    set view [$canv yview]
+    set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
+    set yfrac [expr {$yt / $ymax - $yspan / 2}]
+    if {$yfrac < 0} {
+       set yfrac 0
+    }
+    $canv yview moveto $yfrac
+}
+
 proc lineclick {x y id isnew} {
-    global ctext commitinfo children cflist canv
+    global ctext commitinfo children cflist canv thickerline
 
     unmarkmatches
     unselectline
+    normalline
+    $canv delete hover
+    # draw this line thicker than normal
+    drawlines $id 1
+    set thickerline $id
     if {$isnew} {
-       addtohistory [list lineclick $x $x $id 0]
+       set ymax [lindex [$canv cget -scrollregion] 3]
+       if {$ymax eq {}} return
+       set yfrac [lindex [$canv yview] 0]
+       set y [expr {$y + $yfrac * $ymax}]
+    }
+    set dirn [clickisonarrow $id $y]
+    if {$dirn ne {}} {
+       arrowjump $id $dirn $y
+       return
+    }
+
+    if {$isnew} {
+       addtohistory [list lineclick $x $y $id 0]
     }
-    $canv delete hover
     # fill the details pane with info about this line
     $ctext conf -state normal
     $ctext delete 0.0 end
@@ -3116,6 +3275,14 @@ proc lineclick {x y id isnew} {
     $cflist delete 0 end
 }
 
+proc normalline {} {
+    global thickerline
+    if {[info exists thickerline]} {
+       drawlines $thickerline 0
+       unset thickerline
+    }
+}
+
 proc selbyid {id} {
     global idline
     if {[info exists idline($id)]} {
@@ -3309,7 +3476,6 @@ proc mktag {} {
 
 proc domktag {} {
     global mktagtop env tagids idtags
-    global idpos idline linehtag canv selectedline
 
     set id [$mktagtop.sha1 get]
     set tag [$mktagtop.tag get]
@@ -3334,6 +3500,13 @@ proc domktag {} {
 
     set tagids($tag) $id
     lappend idtags($id) $tag
+    redrawtags $id
+}
+
+proc redrawtags {id} {
+    global canv linehtag idline idpos selectedline
+
+    if {![info exists idline($id)]} return
     $canv delete tag.$id
     set xt [eval drawtags $id $idpos($id)]
     $canv coords $linehtag($idline($id)) $xt [lindex $idpos($id) 2]
@@ -3409,6 +3582,68 @@ proc wrcomcan {} {
     unset wrcomtop
 }
 
+proc listrefs {id} {
+    global idtags idheads idotherrefs
+
+    set x {}
+    if {[info exists idtags($id)]} {
+       set x $idtags($id)
+    }
+    set y {}
+    if {[info exists idheads($id)]} {
+       set y $idheads($id)
+    }
+    set z {}
+    if {[info exists idotherrefs($id)]} {
+       set z $idotherrefs($id)
+    }
+    return [list $x $y $z]
+}
+
+proc rereadrefs {} {
+    global idtags idheads idotherrefs
+    global tagids headids otherrefids
+
+    set refids [concat [array names idtags] \
+                   [array names idheads] [array names idotherrefs]]
+    foreach id $refids {
+       if {![info exists ref($id)]} {
+           set ref($id) [listrefs $id]
+       }
+    }
+    foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
+       catch {unset $v}
+    }
+    readrefs
+    set refids [lsort -unique [concat $refids [array names idtags] \
+                       [array names idheads] [array names idotherrefs]]]
+    foreach id $refids {
+       set v [listrefs $id]
+       if {![info exists ref($id)] || $ref($id) != $v} {
+           redrawtags $id
+       }
+    }
+}
+
+proc showtag {tag isnew} {
+    global ctext cflist tagcontents tagids linknum
+
+    if {$isnew} {
+       addtohistory [list showtag $tag 0]
+    }
+    $ctext conf -state normal
+    $ctext delete 0.0 end
+    set linknum 0
+    if {[info exists tagcontents($tag)]} {
+       set text $tagcontents($tag)
+    } else {
+       set text "Tag: $tag\nId:  $tagids($tag)"
+    }
+    appendwithlinks $text
+    $ctext conf -state disabled
+    $cflist delete 0 end
+}
+
 proc doquit {} {
     global stopped
     set stopped 100