gitk: Fix potential bug with fake commit IDs in renumbervarc
[gitweb.git] / gitk
diff --git a/gitk b/gitk
index 9c5de3f45dd2559fb3381ef1fef28f9bec7588c3..fda06186b4dcf7d42560cd1ac6cf25d06e295925 100755 (executable)
--- a/gitk
+++ b/gitk
@@ -118,7 +118,7 @@ proc start_rev_list {view} {
        set fd [open [concat | git log --no-color -z --pretty=raw --parents \
                         --boundary $commits "--" $viewfiles($view)] r]
     } err]} {
-       error_popup "Error executing git log: $err"
+       error_popup "[mc "Error executing git log:"] $err"
        exit 1
     }
     set i [incr loginstance]
@@ -133,7 +133,7 @@ proc start_rev_list {view} {
        fconfigure $fd -encoding $tclencoding
     }
     filerun $fd [list getcommitlines $fd $i $view]
-    nowbusy $view "Reading"
+    nowbusy $view [mc "Reading"]
     if {$view == $curview} {
        set progressdirn 1
        set progresscoords {0 0}
@@ -163,15 +163,25 @@ proc getcommits {} {
 
     initlayout
     start_rev_list $curview
-    show_status "Reading commits..."
+    show_status [mc "Reading commits..."]
 }
 
 proc updatecommits {} {
     global curview viewargs viewfiles viewincl viewinstances
-    global viewactive viewcomplete loginstance tclencoding
-    global varcid startmsecs commfd getdbg showneartags leftover
+    global viewactive viewcomplete loginstance tclencoding mainheadid
+    global varcid startmsecs commfd showneartags showlocalchanges leftover
+    global mainheadid
 
-    set getdbg 1
+    set oldmainid $mainheadid
+    rereadrefs
+    if {$showlocalchanges} {
+       if {$mainheadid ne $oldmainid} {
+           dohidelocalchanges
+       }
+       if {[commitinview $mainheadid $curview]} {
+           dodiffindex
+       }
+    }
     set view $curview
     set commits [exec git rev-parse --default HEAD --revs-only \
                     $viewargs($view)]
@@ -216,8 +226,6 @@ proc updatecommits {} {
     incr viewactive($view)
     set viewcomplete($view) 0
     nowbusy $view "Reading"
-    readrefs
-    changedrefs
     if {$showneartags} {
        getallcommits
     }
@@ -226,7 +234,7 @@ proc updatecommits {} {
 proc reloadcommits {} {
     global curview viewcomplete selectedline currentid thickerline
     global showneartags treediffs commitinterest cached_commitrow
-    global progresscoords
+    global progresscoords targetid
 
     if {!$viewcomplete($curview)} {
        stop_rev_list $curview
@@ -246,6 +254,7 @@ proc reloadcommits {} {
     clear_display
     catch {unset commitinterest}
     catch {unset cached_commitrow}
+    catch {unset targetid}
     setcanvscroll
     getcommits
 }
@@ -267,21 +276,21 @@ proc strrep {n} {
 # --topo-order) into the order for display.
 
 proc varcinit {view} {
-    global vseeds varcstart vupptr vdownptr vleftptr varctok varcrow
-    global vtokmod varcmod vrowmod varcix uat
+    global varcstart vupptr vdownptr vleftptr vbackptr varctok varcrow
+    global vtokmod varcmod vrowmod varcix vlastins
 
-    set vseeds($view) {}
     set varcstart($view) {{}}
     set vupptr($view) {0}
     set vdownptr($view) {0}
     set vleftptr($view) {0}
+    set vbackptr($view) {0}
     set varctok($view) {{}}
     set varcrow($view) {{}}
     set vtokmod($view) {}
     set varcmod($view) 0
     set vrowmod($view) 0
     set varcix($view) {{}}
-    set uat 0
+    set vlastins($view) {0}
 }
 
 proc resetvarcs {view} {
@@ -306,13 +315,13 @@ proc resetvarcs {view} {
 }
 
 proc newvarc {view id} {
-    global varcid varctok parents children vseeds
-    global vupptr vdownptr vleftptr varcrow varcix varcstart
-    global commitdata commitinfo vseedcount varccommits
+    global varcid varctok parents children datemode
+    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} {
+    if {[llength $children($vid)] == 0 || $datemode} {
        if {![info exists commitinfo($id)]} {
            parsecommit $id $commitdata($id) 1
        }
@@ -326,65 +335,61 @@ proc newvarc {view id} {
        set c [incr vseedcount($view,$cdate)]
        set cdate [expr {$cdate ^ 0xffffffff}]
        set tok "s[strrep $cdate][strrep $c]"
-       lappend vseeds($view) $id
-       lappend vupptr($view) 0
-       set ka [lindex $vdownptr($view) 0]
-       if {$ka == 0 ||
-           [string compare $tok [lindex $varctok($view) $ka]] < 0} {
-           lset vdownptr($view) 0 $a
-           lappend vleftptr($view) $ka
-       } else {
-           while {[set b [lindex $vleftptr($view) $ka]] != 0 &&
-                  [string compare $tok [lindex $varctok($view) $b]] >= 0} {
-               set ka $b
-           }
-           lset vleftptr($view) $ka $a
-           lappend vleftptr($view) $b
-       }
     } else {
        set tok {}
-       foreach k $children($vid) {
-           set ka $varcid($view,$k)
-           if {[string compare [lindex $varctok($view) $ka] $tok] > 0} {
-               set ki $k
-               set tok [lindex $varctok($view) $ka]
-           }
+    }
+    set ka 0
+    if {[llength $children($vid)] > 0} {
+       set kid [lindex $children($vid) end]
+       set k $varcid($view,$kid)
+       if {[string compare [lindex $varctok($view) $k] $tok] > 0} {
+           set ki $kid
+           set ka $k
+           set tok [lindex $varctok($view) $k]
        }
-       set ka $varcid($view,$ki)
-       lappend vupptr($view) $ka
+    }
+    if {$ka != 0} {
        set i [lsearch -exact $parents($view,$ki) $id]
        set j [expr {[llength $parents($view,$ki)] - 1 - $i}]
-       set rsib 0
-       while {[incr i] < [llength $parents($view,$ki)]} {
-           set bi [lindex $parents($view,$ki) $i]
-           if {[info exists varcid($view,$bi)]} {
-               set b $varcid($view,$bi)
-               if {[lindex $vupptr($view) $b] == $ka} {
-                   set rsib $b
-                   lappend vleftptr($view) [lindex $vleftptr($view) $b]
-                   lset vleftptr($view) $b $a
-                   break
-               }
-           }
-       }
-       if {$rsib == 0} {
-           lappend vleftptr($view) [lindex $vdownptr($view) $ka]
-           lset vdownptr($view) $ka $a
-       }
        append tok [strrep $j]
     }
+    set c [lindex $vlastins($view) $ka]
+    if {$c == 0 || [string compare $tok [lindex $varctok($view) $c]] < 0} {
+       set c $ka
+       set b [lindex $vdownptr($view) $ka]
+    } else {
+       set b [lindex $vleftptr($view) $c]
+    }
+    while {$b != 0 && [string compare $tok [lindex $varctok($view) $b]] >= 0} {
+       set c $b
+       set b [lindex $vleftptr($view) $c]
+    }
+    if {$c == $ka} {
+       lset vdownptr($view) $ka $a
+       lappend vbackptr($view) 0
+    } else {
+       lset vleftptr($view) $c $a
+       lappend vbackptr($view) $c
+    }
+    lset vlastins($view) $ka $a
+    lappend vupptr($view) $ka
+    lappend vleftptr($view) $b
+    if {$b != 0} {
+       lset vbackptr($view) $b $a
+    }
     lappend varctok($view) $tok
     lappend varcstart($view) $id
     lappend vdownptr($view) 0
     lappend varcrow($view) {}
     lappend varcix($view) {}
     set varccommits($view,$a) {}
+    lappend vlastins($view) 0
     return $a
 }
 
 proc splitvarc {p v} {
     global varcid varcstart varccommits varctok
-    global vupptr vdownptr vleftptr varcix varcrow
+    global vupptr vdownptr vleftptr vbackptr varcix varcrow vlastins
 
     set oa $varcid($v,$p)
     set ac $varccommits($v,$oa)
@@ -406,6 +411,8 @@ proc splitvarc {p v} {
     lset vdownptr($v) $oa $na
     lappend vupptr($v) $oa
     lappend vleftptr($v) 0
+    lappend vbackptr($v) 0
+    lappend vlastins($v) 0
     for {set b [lindex $vdownptr($v) $na]} {$b != 0} {set b [lindex $vleftptr($v) $b]} {
        lset vupptr($v) $b $na
     }
@@ -413,11 +420,12 @@ proc splitvarc {p v} {
 
 proc renumbervarc {a v} {
     global parents children varctok varcstart varccommits
-    global vupptr vdownptr vleftptr varcid vtokmod
+    global vupptr vdownptr vleftptr vbackptr vlastins varcid vtokmod datemode
 
     set t1 [clock clicks -milliseconds]
     set todo {}
     set isrelated($a) 1
+    set kidchanged($a) 1
     set ntot 0
     while {$a != 0} {
        if {[info exists isrelated($a)]} {
@@ -441,26 +449,45 @@ proc renumbervarc {a v} {
        set a $b
     }
     foreach a $todo {
+       if {![info exists kidchanged($a)]} continue
        set id [lindex $varcstart($v) $a]
-       set tok {}
-       foreach k $children($v,$id) {
-           set ka $varcid($v,$k)
-           if {[string compare [lindex $varctok($v) $ka] $tok] > 0} {
-               set ki $k
-               set tok [lindex $varctok($v) $ka]
+       if {[llength $children($v,$id)] > 1} {
+           set children($v,$id) [lsort -command [list vtokcmp $v] \
+                                     $children($v,$id)]
+       }
+       set oldtok [lindex $varctok($v) $a]
+       if {!$datemode} {
+           set tok {}
+       } else {
+           set tok $oldtok
+       }
+       set ka 0
+       set kid [last_real_child $v,$id]
+       if {$kid ne {}} {
+           set k $varcid($v,$kid)
+           if {[string compare [lindex $varctok($v) $k] $tok] > 0} {
+               set ki $kid
+               set ka $k
+               set tok [lindex $varctok($v) $k]
            }
        }
-       if {$tok ne {}} {
-           set ka $varcid($v,$ki)
+       if {$ka != 0} {
            set i [lsearch -exact $parents($v,$ki) $id]
            set j [expr {[llength $parents($v,$ki)] - 1 - $i}]
            append tok [strrep $j]
-           set oldtok [lindex $varctok($v) $a]
-           if {$tok eq $oldtok} continue
-           lset varctok($v) $a $tok
-       } else {
-           set ka 0
        }
+       if {$tok eq $oldtok} {
+           continue
+       }
+       set id [lindex $varccommits($v,$a) end]
+       foreach p $parents($v,$id) {
+           if {[info exists varcid($v,$p)]} {
+               set kidchanged($varcid($v,$p)) 1
+           } else {
+               set sortkids($p) 1
+           }
+       }
+       lset varctok($v) $a $tok
        set b [lindex $vupptr($v) $a]
        if {$b != $ka} {
            if {[string compare [lindex $varctok($v) $ka] $vtokmod($v)] < 0} {
@@ -469,38 +496,48 @@ proc renumbervarc {a v} {
            if {[string compare [lindex $varctok($v) $b] $vtokmod($v)] < 0} {
                modify_arc $v $b
            }
-           set c [lindex $vdownptr($v) $b]
-           if {$c == $a} {
-               lset vdownptr($v) $b [lindex $vleftptr($v) $a]
+           set c [lindex $vbackptr($v) $a]
+           set d [lindex $vleftptr($v) $a]
+           if {$c == 0} {
+               lset vdownptr($v) $b $d
            } else {
-               set b $c
-               while {$b != 0 && [lindex $vleftptr($v) $b] != $a} {
-                   set b [lindex $vleftptr($v) $b]
-               }
-               if {$b != 0} {
-                   lset vleftptr($v) $b [lindex $vleftptr($v) $a]
-               } else {
-                   puts "oops couldn't find $a in chain for [lindex $vupptr($v) $a]"
-               }
+               lset vleftptr($v) $c $d
+           }
+           if {$d != 0} {
+               lset vbackptr($v) $d $c
            }
            lset vupptr($v) $a $ka
-           set rsib 0
-           while {[incr i] < [llength $parents($v,$ki)]} {
-               set bi [lindex $parents($v,$ki) $i]
-               if {[info exists varcid($v,$bi)]} {
-                   set b $varcid($v,$bi)
-                   if {[lindex $vupptr($v) $b] == $ka} {
-                       set rsib $b
-                       lset vleftptr($v) $a [lindex $vleftptr($v) $b]
-                       lset vleftptr($v) $b $a
-                       break
-                   }
-               }
+           set c [lindex $vlastins($v) $ka]
+           if {$c == 0 || \
+                   [string compare $tok [lindex $varctok($v) $c]] < 0} {
+               set c $ka
+               set b [lindex $vdownptr($v) $ka]
+           } else {
+               set b [lindex $vleftptr($v) $c]
+           }
+           while {$b != 0 && \
+                     [string compare $tok [lindex $varctok($v) $b]] >= 0} {
+               set c $b
+               set b [lindex $vleftptr($v) $c]
+           }
+           if {$c == $ka} {
+               lset vdownptr($v) $ka $a
+               lset vbackptr($v) $a 0
+           } else {
+               lset vleftptr($v) $c $a
+               lset vbackptr($v) $a $c
            }
-           if {$rsib == 0} {
-               lset vleftptr($v) $a [lindex $vdownptr($v) $ka]
-               lset vdownptr($v) $ka $a
+           lset vleftptr($v) $a $b
+           if {$b != 0} {
+               lset vbackptr($v) $b $a
            }
+           lset vlastins($v) $ka $a
+       }
+    }
+    foreach id [array names sortkids] {
+       if {[llength $children($v,$id)] > 1} {
+           set children($v,$id) [lsort -command [list vtokcmp $v] \
+                                     $children($v,$id)]
        }
     }
     set t2 [clock clicks -milliseconds]
@@ -508,31 +545,24 @@ proc renumbervarc {a v} {
 }
 
 proc fix_reversal {p a v} {
-    global varcid varcstart varctok vupptr vseeds
+    global varcid varcstart varctok vupptr
 
     set pa $varcid($v,$p)
     if {$p ne [lindex $varcstart($v) $pa]} {
        splitvarc $p $v
        set pa $varcid($v,$p)
     }
-    # seeds always need to be renumbered (and taken out of the seeds list)
-    if {[lindex $vupptr($v) $pa] == 0} {
-       set i [lsearch -exact $vseeds($v) $p]
-       if {$i >= 0} {
-           set vseeds($v) [lreplace $vseeds($v) $i $i]
-       } else {
-           puts "oops couldn't find [shortids $p] in seeds"
-       }
-       renumbervarc $pa $v
-    } elseif {[string compare [lindex $varctok($v) $a] \
-                  [lindex $varctok($v) $pa]] > 0} {
+    # seeds always need to be renumbered
+    if {[lindex $vupptr($v) $pa] == 0 ||
+       [string compare [lindex $varctok($v) $a] \
+            [lindex $varctok($v) $pa]] > 0} {
        renumbervarc $pa $v
     }
 }
 
 proc insertrow {id p v} {
     global varcid varccommits parents children cmitlisted
-    global commitidx varctok vtokmod
+    global commitidx varctok vtokmod targetid targetrow
 
     set a $varcid($v,$p)
     set i [lsearch -exact $varccommits($v,$a) $p]
@@ -551,12 +581,18 @@ proc insertrow {id p v} {
     if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] < 0} {
        modify_arc $v $a $i
     }
+    if {[info exists targetid]} {
+       if {![comes_before $targetid $p]} {
+           incr targetrow
+       }
+    }
     drawvisible
 }
 
 proc removerow {id v} {
     global varcid varccommits parents children commitidx
-    global varctok vtokmod
+    global varctok vtokmod cmitlisted currentid selectedline
+    global targetid
 
     if {[llength $parents($v,$id)] != 1} {
        puts "oops: removerow [shortids $id] has [llength $parents($v,$id)] parents"
@@ -579,13 +615,43 @@ proc removerow {id v} {
     if {$j >= 0} {
        set children($v,$p) [lreplace $children($v,$p) $j $j]
     }
-    set tok [lindex $varctok($v) $a]
     if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] < 0} {
        modify_arc $v $a $i
     }
+    if {[info exist currentid] && $id eq $currentid} {
+       unset currentid
+       unset selectedline
+    }
+    if {[info exists targetid] && $targetid eq $id} {
+       set targetid $p
+    }
     drawvisible
 }
 
+proc first_real_child {vp} {
+    global children nullid nullid2
+
+    foreach id $children($vp) {
+       if {$id ne $nullid && $id ne $nullid2} {
+           return $id
+       }
+    }
+    return {}
+}
+
+proc last_real_child {vp} {
+    global children nullid nullid2
+
+    set kids $children($vp)
+    for {set i [llength $kids]} {[incr i -1] >= 0} {} {
+       set id [lindex $kids $i]
+       if {$id ne $nullid && $id ne $nullid2} {
+           return $id
+       }
+    }
+    return {}
+}
+
 proc vtokcmp {v a b} {
     global varctok varcid
 
@@ -595,6 +661,7 @@ proc vtokcmp {v a b} {
 
 proc modify_arc {v a {lim {}}} {
     global varctok vtokmod varcmod varcrow vupptr curview vrowmod varccommits
+    global vhighlights nhighlights fhighlights rhighlights
 
     set vtokmod($v) [lindex $varctok($v) $a]
     set varcmod($v) $a
@@ -613,15 +680,18 @@ proc modify_arc {v a {lim {}}} {
        set vrowmod($v) $r
        undolayout $r
     }
+    catch {unset nhighlights}
+    catch {unset fhighlights}
+    catch {unset vhighlights}
+    catch {unset rhighlights}
 }
 
 proc update_arcrows {v} {
     global vtokmod varcmod vrowmod varcrow commitidx currentid selectedline
-    global varcid vseeds vrownum varcorder varcix varccommits
+    global varcid vrownum varcorder varcix varccommits
     global vupptr vdownptr vleftptr varctok
-    global uat displayorder parentlist curview cached_commitrow
+    global displayorder parentlist curview cached_commitrow
 
-    set t1 [clock clicks -milliseconds]
     set narctot [expr {[llength $varctok($v)] - 1}]
     set a $varcmod($v)
     while {$a != 0 && [lindex $varcix($v) $a] eq {}} {
@@ -684,8 +754,6 @@ proc update_arcrows {v} {
     if {[info exists currentid]} {
        set selectedline [rowofcommit $currentid]
     }
-    set t2 [clock clicks -milliseconds]
-    incr uat [expr {$t2-$t1}]
 }
 
 # Test whether view $v contains commit $id
@@ -700,18 +768,18 @@ proc rowofcommit {id} {
     global varcid varccommits varcrow curview cached_commitrow
     global varctok vtokmod
 
-    if {[info exists cached_commitrow($id)]} {
-       return $cached_commitrow($id)
-    }
     set v $curview
     if {![info exists varcid($v,$id)]} {
        puts "oops rowofcommit no arc for [shortids $id]"
        return {}
     }
     set a $varcid($v,$id)
-    if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] > 0} {
+    if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] >= 0} {
        update_arcrows $v
     }
+    if {[info exists cached_commitrow($id)]} {
+       return $cached_commitrow($id)
+    }
     set i [lsearch -exact $varccommits($v,$a) $id]
     if {$i < 0} {
        puts "oops didn't find commit [shortids $id] in arc $a"
@@ -722,6 +790,22 @@ proc rowofcommit {id} {
     return $i
 }
 
+# Returns 1 if a is on an earlier row than b, otherwise 0
+proc comes_before {a b} {
+    global varcid varctok curview
+
+    set v $curview
+    if {$a eq $b || ![info exists varcid($v,$a)] || \
+           ![info exists varcid($v,$b)]} {
+       return 0
+    }
+    if {$varcid($v,$a) != $varcid($v,$b)} {
+       return [expr {[string compare [lindex $varctok($v) $varcid($v,$a)] \
+                          [lindex $varctok($v) $varcid($v,$b)]] < 0}]
+    }
+    return [expr {[rowofcommit $a] < [rowofcommit $b]}]
+}
+
 proc bsearch {l elt} {
     if {[llength $l] == 0 || $elt <= [lindex $l 0]} {
        return 0
@@ -837,8 +921,8 @@ proc closevarcs {v} {
 }
 
 proc getcommitlines {fd inst view}  {
-    global cmitlisted commitinterest leftover getdbg
-    global commitidx commitdata
+    global cmitlisted commitinterest leftover
+    global commitidx commitdata datemode
     global parents children curview hlview
     global vnextroot idpending ordertok
     global varccommits varcid varctok vtokmod
@@ -936,7 +1020,7 @@ proc getcommitlines {fd inst view}  {
            if {[string length $shortcmit] > 80} {
                set shortcmit "[string range $shortcmit 0 80]..."
            }
-           error_popup "Can't parse git log output: {$shortcmit}"
+           error_popup "[mc "Can't parse git log output:"] {$shortcmit}"
            exit 1
        }
        set id [lindex $ids 0]
@@ -953,12 +1037,12 @@ proc getcommitlines {fd inst view}  {
        set a 0
        if {![info exists children($vid)]} {
            set children($vid) {}
-       } else {
-           if {[llength $children($vid)] == 1} {
-               set k [lindex $children($vid) 0]
-               if {[llength $parents($view,$k)] == 1} {
-                   set a $varcid($view,$k)
-               }
+       } elseif {[llength $children($vid)] == 1} {
+           set k [lindex $children($vid) 0]
+           if {[llength $parents($view,$k)] == 1 &&
+               (!$datemode ||
+                $varcid($view,$k) == [llength $varctok($view)] - 1)} {
+               set a $varcid($view,$k)
            }
        }
        if {$a == 0} {
@@ -981,9 +1065,9 @@ proc getcommitlines {fd inst view}  {
                                           $children($vp)]
                    catch {unset ordertok}
                }
-           }
-           if {[info exists varcid($view,$p)]} {
-               fix_reversal $p $a $view
+               if {[info exists varcid($view,$p)]} {
+                   fix_reversal $p $a $view
+               }
            }
            incr i
        }
@@ -1040,7 +1124,7 @@ proc chewcommits {view} {
     if {$view == $curview} {
        layoutmore
        if {$viewcomplete($view)} {
-           global commitidx
+           global commitidx varctok
            global numcommits startmsecs
            global mainheadid commitinfo nullid
 
@@ -1051,10 +1135,9 @@ proc chewcommits {view} {
            if {$commitidx($curview) > 0} {
                #set ms [expr {[clock clicks -milliseconds] - $startmsecs}]
                #puts "overall $ms ms for $numcommits commits"
-               #global uat
-               #puts "${uat}ms in update_arcrows"
+               #puts "[llength $varctok($view)] arcs, $commitidx($view) commits"
            } else {
-               show_status "No commits selected"
+               show_status [mc "No commits selected"]
            }
            notbusy layout
        }
@@ -1135,7 +1218,7 @@ proc getcommit {id} {
     } else {
        readcommit $id
        if {![info exists commitinfo($id)]} {
-           set commitinfo($id) {"No commit information available"}
+           set commitinfo($id) [list [mc "No commit information available"]]
        }
     }
     return 1
@@ -1234,7 +1317,7 @@ proc removehead {id name} {
 proc show_error {w top msg} {
     message $w.m -text $msg -justify center -aspect 400
     pack $w.m -side top -fill x -padx 20 -pady 20
-    button $w.ok -text OK -command "destroy $top"
+    button $w.ok -text [mc OK] -command "destroy $top"
     pack $w.ok -side bottom -fill x
     bind $top <Visibility> "grab $top; focus $top"
     bind $top <Key-Return> "destroy $top"
@@ -1256,9 +1339,9 @@ proc confirm_popup msg {
     wm transient $w .
     message $w.m -text $msg -justify center -aspect 400
     pack $w.m -side top -fill x -padx 20 -pady 20
-    button $w.ok -text OK -command "set confirm_ok 1; destroy $w"
+    button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
     pack $w.ok -side left -fill x
-    button $w.cancel -text Cancel -command "destroy $w"
+    button $w.cancel -text [mc Cancel] -command "destroy $w"
     pack $w.cancel -side right -fill x
     bind $w <Visibility> "grab $w; focus $w"
     tkwait window $w
@@ -1266,7 +1349,7 @@ proc confirm_popup msg {
 }
 
 proc makewindow {} {
-    global canv canv2 canv3 linespc charspc ctext cflist
+    global canv canv2 canv3 linespc charspc ctext cflist cscroll
     global tabstop
     global findtype findtypemenu findloc findstring fstring geometry
     global entries sha1entry sha1string sha1but
@@ -1282,34 +1365,34 @@ proc makewindow {} {
     global have_tk85
 
     menu .bar
-    .bar add cascade -label "File" -menu .bar.file
+    .bar add cascade -label [mc "File"] -menu .bar.file
     .bar configure -font uifont
     menu .bar.file
-    .bar.file add command -label "Update" -command updatecommits
-    .bar.file add command -label "Reload" -command reloadcommits
-    .bar.file add command -label "Reread references" -command rereadrefs
-    .bar.file add command -label "List references" -command showrefs
-    .bar.file add command -label "Quit" -command doquit
+    .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
     .bar.file configure -font uifont
     menu .bar.edit
-    .bar add cascade -label "Edit" -menu .bar.edit
-    .bar.edit add command -label "Preferences" -command doprefs
+    .bar add cascade -label [mc "Edit"] -menu .bar.edit
+    .bar.edit add command -label [mc "Preferences"] -command doprefs
     .bar.edit configure -font uifont
 
     menu .bar.view -font uifont
-    .bar add cascade -label "View" -menu .bar.view
-    .bar.view add command -label "New view..." -command {newview 0}
-    .bar.view add command -label "Edit view..." -command editview \
+    .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 "Delete view" -command delview -state disabled
+    .bar.view add command -label [mc "Delete view"] -command delview -state disabled
     .bar.view add separator
-    .bar.view add radiobutton -label "All files" -command {showview 0} \
+    .bar.view add radiobutton -label [mc "All files"] -command {showview 0} \
        -variable selectedview -value 0
 
     menu .bar.help
-    .bar add cascade -label "Help" -menu .bar.help
-    .bar.help add command -label "About gitk" -command about
-    .bar.help add command -label "Key bindings" -command keys
+    .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 -font uifont
     . configure -menu .bar
 
@@ -1366,7 +1449,7 @@ proc makewindow {} {
     set sha1entry .tf.bar.sha1
     set entries $sha1entry
     set sha1but .tf.bar.sha1label
-    button $sha1but -text "SHA1 ID: " -state disabled -relief flat \
+    button $sha1but -text [mc "SHA1 ID: "] -state disabled -relief flat \
        -command gotocommit -width 8 -font uifont
     $sha1but conf -disabledforeground [$sha1but cget -foreground]
     pack .tf.bar.sha1label -side left
@@ -1416,17 +1499,17 @@ proc makewindow {} {
     set progupdatepending 0
 
     # build up the bottom bar of upper window
-    label .tf.lbar.flabel -text "Find " -font uifont
-    button .tf.lbar.fnext -text "next" -command {dofind 1 1} -font uifont
-    button .tf.lbar.fprev -text "prev" -command {dofind -1 1} -font uifont
-    label .tf.lbar.flab2 -text " commit " -font uifont
+    label .tf.lbar.flabel -text "[mc "Find"] " -font uifont
+    button .tf.lbar.fnext -text [mc "next"] -command {dofind 1 1} -font uifont
+    button .tf.lbar.fprev -text [mc "prev"] -command {dofind -1 1} -font uifont
+    label .tf.lbar.flab2 -text " [mc "commit"] " -font uifont
     pack .tf.lbar.flabel .tf.lbar.fnext .tf.lbar.fprev .tf.lbar.flab2 \
        -side left -fill y
-    set gdttype "containing:"
+    set gdttype [mc "containing:"]
     set gm [tk_optionMenu .tf.lbar.gdttype gdttype \
-               "containing:" \
-               "touching paths:" \
-               "adding/removing string:"]
+               [mc "containing:"] \
+               [mc "touching paths:"] \
+               [mc "adding/removing string:"]]
     trace add variable gdttype write gdttype_change
     $gm conf -font uifont
     .tf.lbar.gdttype conf -font uifont
@@ -1437,15 +1520,15 @@ proc makewindow {} {
     lappend entries $fstring
     entry $fstring -width 30 -font textfont -textvariable findstring
     trace add variable findstring write find_change
-    set findtype Exact
+    set findtype [mc "Exact"]
     set findtypemenu [tk_optionMenu .tf.lbar.findtype \
-                     findtype Exact IgnCase Regexp]
+                     findtype [mc "Exact"] [mc "IgnCase"] [mc "Regexp"]]
     trace add variable findtype write findcom_change
     .tf.lbar.findtype configure -font uifont
     .tf.lbar.findtype.menu configure -font uifont
-    set findloc "All fields"
-    tk_optionMenu .tf.lbar.findloc findloc "All fields" Headline \
-       Comments Author Committer
+    set findloc [mc "All fields"]
+    tk_optionMenu .tf.lbar.findloc findloc [mc "All fields"] [mc "Headline"] \
+       [mc "Comments"] [mc "Author"] [mc "Committer"]
     trace add variable findloc write find_change
     .tf.lbar.findloc configure -font uifont
     .tf.lbar.findloc.menu configure -font uifont
@@ -1475,7 +1558,7 @@ proc makewindow {} {
     frame .bleft.top
     frame .bleft.mid
 
-    button .bleft.top.search -text "Search" -command dosearch \
+    button .bleft.top.search -text [mc "Search"] -command dosearch \
        -font uifont
     pack .bleft.top.search -side left -padx 5
     set sstring .bleft.top.sstring
@@ -1483,13 +1566,13 @@ proc makewindow {} {
     lappend entries $sstring
     trace add variable searchstring write incrsearch
     pack $sstring -side left -expand 1 -fill x
-    radiobutton .bleft.mid.diff -text "Diff" -font uifont \
+    radiobutton .bleft.mid.diff -text [mc "Diff"] -font uifont \
        -command changediffdisp -variable diffelide -value {0 0}
-    radiobutton .bleft.mid.old -text "Old version" -font uifont \
+    radiobutton .bleft.mid.old -text [mc "Old version"] -font uifont \
        -command changediffdisp -variable diffelide -value {0 1}
-    radiobutton .bleft.mid.new -text "New version" -font uifont \
+    radiobutton .bleft.mid.new -text [mc "New version"] -font uifont \
        -command changediffdisp -variable diffelide -value {1 0}
-    label .bleft.mid.labeldiffcontext -text "      Lines of context: " \
+    label .bleft.mid.labeldiffcontext -text "      [mc "Lines of context"]: " \
        -font uifont
     pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left
     spinbox .bleft.mid.diffcontext -width 5 -font textfont \
@@ -1548,10 +1631,10 @@ proc makewindow {} {
     # lower right
     frame .bright
     frame .bright.mode
-    radiobutton .bright.mode.patch -text "Patch" \
+    radiobutton .bright.mode.patch -text [mc "Patch"] \
        -command reselectline -variable cmitmode -value "patch"
     .bright.mode.patch configure -font uifont
-    radiobutton .bright.mode.tree -text "Tree" \
+    radiobutton .bright.mode.tree -text [mc "Tree"] \
        -command reselectline -variable cmitmode -value "tree"
     .bright.mode.tree configure -font uifont
     grid .bright.mode.patch .bright.mode.tree -sticky ew
@@ -1668,43 +1751,43 @@ proc makewindow {} {
 
     set rowctxmenu .rowctxmenu
     menu $rowctxmenu -tearoff 0
-    $rowctxmenu add command -label "Diff this -> selected" \
+    $rowctxmenu add command -label [mc "Diff this -> selected"] \
        -command {diffvssel 0}
-    $rowctxmenu add command -label "Diff selected -> this" \
+    $rowctxmenu add command -label [mc "Diff selected -> this"] \
        -command {diffvssel 1}
-    $rowctxmenu add command -label "Make patch" -command mkpatch
-    $rowctxmenu add command -label "Create tag" -command mktag
-    $rowctxmenu add command -label "Write commit to file" -command writecommit
-    $rowctxmenu add command -label "Create new branch" -command mkbranch
-    $rowctxmenu add command -label "Cherry-pick this commit" \
+    $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 "Reset HEAD branch to here" \
+    $rowctxmenu add command -label [mc "Reset HEAD branch to here"] \
        -command resethead
 
     set fakerowmenu .fakerowmenu
     menu $fakerowmenu -tearoff 0
-    $fakerowmenu add command -label "Diff this -> selected" \
+    $fakerowmenu add command -label [mc "Diff this -> selected"] \
        -command {diffvssel 0}
-    $fakerowmenu add command -label "Diff selected -> this" \
+    $fakerowmenu add command -label [mc "Diff selected -> this"] \
        -command {diffvssel 1}
-    $fakerowmenu add command -label "Make patch" -command mkpatch
-#    $fakerowmenu add command -label "Commit" -command {mkcommit 0}
-#    $fakerowmenu add command -label "Commit all" -command {mkcommit 1}
-#    $fakerowmenu add command -label "Revert local changes" -command revertlocal
+    $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
 
     set headctxmenu .headctxmenu
     menu $headctxmenu -tearoff 0
-    $headctxmenu add command -label "Check out this branch" \
+    $headctxmenu add command -label [mc "Check out this branch"] \
        -command cobranch
-    $headctxmenu add command -label "Remove this branch" \
+    $headctxmenu add command -label [mc "Remove this branch"] \
        -command rmbranch
 
     global flist_menu
     set flist_menu .flistctxmenu
     menu $flist_menu -tearoff 0
-    $flist_menu add command -label "Highlight this too" \
+    $flist_menu add command -label [mc "Highlight this too"] \
        -command {flist_hl 0}
-    $flist_menu add command -label "Highlight this only" \
+    $flist_menu add command -label [mc "Highlight this only"] \
        -command {flist_hl 1}
 }
 
@@ -1741,7 +1824,7 @@ proc canvscan {op w x y} {
 
 proc scrollcanv {cscroll f0 f1} {
     $cscroll set $f0 $f1
-    drawfrac $f0 $f1
+    drawvisible
     flushhighlights
 }
 
@@ -1930,17 +2013,17 @@ proc about {} {
        return
     }
     toplevel $w
-    wm title $w "About gitk"
-    message $w.m -text {
+    wm title $w [mc "About gitk"]
+    message $w.m -text [mc "
 Gitk - a commit viewer for git
 
-Copyright Â© 2005-2007 Paul Mackerras
+Copyright Â© 2005-2006 Paul Mackerras
 
-Use and redistribute under the terms of the GNU General Public License} \
+Use and redistribute under the terms of the GNU General Public License"] \
            -justify center -aspect 400 -border 2 -bg white -relief groove
     pack $w.m -side top -fill x -padx 2 -pady 2
     $w.m configure -font uifont
-    button $w.ok -text Close -command "destroy $w" -default active
+    button $w.ok -text [mc "Close"] -command "destroy $w" -default active
     pack $w.ok -side bottom
     $w.ok configure -font uifont
     bind $w <Visibility> "focus $w.ok"
@@ -1961,8 +2044,8 @@ proc keys {} {
        set M1T Ctrl
     }
     toplevel $w
-    wm title $w "Gitk key bindings"
-    message $w.m -text "
+    wm title $w [mc "Gitk key bindings"]
+    message $w.m -text [mc "
 Gitk key bindings:
 
 <$M1T-Q>               Quit
@@ -2000,11 +2083,11 @@ f               Scroll diff view to next file
 <$M1T-KP->     Decrease font size
 <$M1T-minus>   Decrease font size
 <F5>           Update
-" \
+"] \
            -justify left -bg white -border 2 -relief groove
     pack $w.m -side top -fill both -padx 2 -pady 2
     $w.m configure -font uifont
-    button $w.ok -text Close -command "destroy $w" -default active
+    button $w.ok -text [mc "Close"] -command "destroy $w" -default active
     pack $w.ok -side bottom
     $w.ok configure -font uifont
     bind $w <Visibility> "focus $w.ok"
@@ -2396,12 +2479,12 @@ proc flist_hl {only} {
     global flist_menu_file findstring gdttype
 
     set x [shellquote $flist_menu_file]
-    if {$only || $findstring eq {} || $gdttype ne "touching paths:"} {
+    if {$only || $findstring eq {} || $gdttype ne [mc "touching paths:"]} {
        set findstring $x
     } else {
        append findstring " " $x
     }
-    set gdttype "touching paths:"
+    set gdttype [mc "touching paths:"]
 }
 
 # Functions for adding and removing shell-type quoting
@@ -2513,7 +2596,7 @@ proc newview {ishighlight} {
     set newviewname($nextviewnum) "View $nextviewnum"
     set newviewperm($nextviewnum) 0
     set newviewargs($nextviewnum) [shellarglist $revtreeargs]
-    vieweditor $top $nextviewnum "Gitk view definition"
+    vieweditor $top $nextviewnum [mc "Gitk view definition"]
 }
 
 proc editview {} {
@@ -2538,20 +2621,20 @@ proc vieweditor {top n title} {
 
     toplevel $top
     wm title $top $title
-    label $top.nl -text "Name" -font uifont
+    label $top.nl -text [mc "Name"] -font uifont
     entry $top.name -width 20 -textvariable newviewname($n) -font uifont
     grid $top.nl $top.name -sticky w -pady 5
-    checkbutton $top.perm -text "Remember this view" -variable newviewperm($n) \
+    checkbutton $top.perm -text [mc "Remember this view"] -variable newviewperm($n) \
        -font uifont
     grid $top.perm - -pady 5 -sticky w
     message $top.al -aspect 1000 -font uifont \
-       -text "Commits to include (arguments to git rev-list):"
+       -text [mc "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 -font uifont
     grid $top.args - -sticky ew -padx 5
     message $top.l -aspect 1000 -font uifont \
-       -text "Enter files and directories to include, one per line:"
+       -text [mc "Enter files and directories to include, one per line:"]
     grid $top.l - -sticky w
     text $top.t -width 40 -height 10 -background white -font uifont
     if {[info exists viewfiles($n)]} {
@@ -2564,9 +2647,9 @@ proc vieweditor {top n title} {
     }
     grid $top.t - -sticky ew -padx 5
     frame $top.buts
-    button $top.buts.ok -text "OK" -command [list newviewok $top $n] \
+    button $top.buts.ok -text [mc "OK"] -command [list newviewok $top $n] \
        -font uifont
-    button $top.buts.can -text "Cancel" -command [list destroy $top] \
+    button $top.buts.can -text [mc "Cancel"] -command [list destroy $top] \
        -font uifont
     grid $top.buts.ok $top.buts.can
     grid columnconfigure $top.buts 0 -weight 1 -uniform a
@@ -2600,7 +2683,7 @@ proc newviewok {top n} {
     if {[catch {
        set newargs [shellsplit $newviewargs($n)]
     } err]} {
-       error_popup "Error in commit selection arguments: $err"
+       error_popup "[mc "Error in commit selection arguments:"] $err"
        wm raise $top
        focus $top
        return
@@ -2651,7 +2734,7 @@ proc delview {} {
 
     if {$curview == 0} return
     if {[info exists hlview] && $hlview == $curview} {
-       set selectedhlview None
+       set selectedhlview [mc "None"]
        unset hlview
     }
     allviewmenus $curview delete
@@ -2703,7 +2786,7 @@ proc showview {n} {
     clear_display
     if {[info exists hlview] && $hlview == $n} {
        unset hlview
-       set selectedhlview None
+       set selectedhlview [mc "None"]
     }
     catch {unset commitinterest}
     catch {unset cached_commitrow}
@@ -2711,8 +2794,8 @@ proc showview {n} {
 
     set curview $n
     set selectedview $n
-    .bar.view entryconf Edit* -state [expr {$n == 0? "disabled": "normal"}]
-    .bar.view entryconf Delete* -state [expr {$n == 0? "disabled": "normal"}]
+    .bar.view entryconf [mc "Edit view..."] -state [expr {$n == 0? "disabled": "normal"}]
+    .bar.view entryconf [mc "Delete view"] -state [expr {$n == 0? "disabled": "normal"}]
 
     run refill_reflist
     if {![info exists viewcomplete($n)]} {
@@ -2766,12 +2849,10 @@ proc showview {n} {
     }
     if {!$viewcomplete($n)} {
        if {$numcommits == 0} {
-           show_status "Reading commits..."
-       } else {
-           run chewcommits $n
+           show_status [mc "Reading commits..."]
        }
     } elseif {$numcommits == 0} {
-       show_status "No commits selected"
+       show_status [mc "No commits selected"]
     }
 }
 
@@ -2922,7 +3003,7 @@ proc gdttype_change {name ix op} {
 
     stopfinding
     if {$findstring ne {}} {
-       if {$gdttype eq "containing:"} {
+       if {$gdttype eq [mc "containing:"]} {
            if {$highlight_files ne {}} {
                set highlight_files {}
                hfiles_change
@@ -2945,7 +3026,7 @@ proc find_change {name ix op} {
     global gdttype findstring highlight_files
 
     stopfinding
-    if {$gdttype eq "containing:"} {
+    if {$gdttype eq [mc "containing:"]} {
        findcom_change
     } else {
        if {$highlight_files ne $findstring} {
@@ -2969,9 +3050,9 @@ proc findcom_change args {
     catch {unset nhighlights}
     unbolden
     unmarkmatches
-    if {$gdttype ne "containing:" || $findstring eq {}} {
+    if {$gdttype ne [mc "containing:"] || $findstring eq {}} {
        set findpattern {}
-    } elseif {$findtype eq "Regexp"} {
+    } elseif {$findtype eq [mc "Regexp"]} {
        set findpattern $findstring
     } else {
        set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \
@@ -2997,12 +3078,12 @@ proc makepatterns {l} {
 proc do_file_hl {serial} {
     global highlight_files filehighlight highlight_paths gdttype fhl_list
 
-    if {$gdttype eq "touching paths:"} {
+    if {$gdttype eq [mc "touching paths:"]} {
        if {[catch {set paths [shellsplit $highlight_files]}]} return
        set highlight_paths [makepatterns $paths]
        highlight_filelist
        set gdtargs [concat -- $paths]
-    } elseif {$gdttype eq "adding/removing string:"} {
+    } elseif {$gdttype eq [mc "adding/removing string:"]} {
        set gdtargs [list "-S$highlight_files"]
     } else {
        # must be "containing:", i.e. we're searching commit info
@@ -3078,9 +3159,9 @@ proc readfhighlight {} {
 proc doesmatch {f} {
     global findtype findpattern
 
-    if {$findtype eq "Regexp"} {
+    if {$findtype eq [mc "Regexp"]} {
        return [regexp $findpattern $f]
-    } elseif {$findtype eq "IgnCase"} {
+    } elseif {$findtype eq [mc "IgnCase"]} {
        return [string match -nocase $findpattern $f]
     } else {
        return [string match $findpattern $f]
@@ -3097,11 +3178,11 @@ proc askfindhighlight {row id} {
     }
     set info $commitinfo($id)
     set isbold 0
-    set fldtypes {Headline Author Date Committer CDate Comments}
+    set fldtypes [list [mc Headline] [mc Author] [mc Date] [mc Committer] [mc CDate] [mc Comments]]
     foreach f $info ty $fldtypes {
-       if {($findloc eq "All fields" || $findloc eq $ty) &&
+       if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
            [doesmatch $f]} {
-           if {$ty eq "Author"} {
+           if {$ty eq [mc "Author"]} {
                set isbold 2
                break
            }
@@ -3129,14 +3210,14 @@ proc markrowmatches {row id} {
     set author [lindex $commitinfo($id) 1]
     $canv delete match$row
     $canv2 delete match$row
-    if {$findloc eq "All fields" || $findloc eq "Headline"} {
+    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
        }
     }
-    if {$findloc eq "All fields" || $findloc eq "Author"} {
+    if {$findloc eq [mc "All fields"] || $findloc eq [mc "Author"]} {
        set m [findmatches $author]
        if {$m ne {}} {
            markmatches $canv2 $row $author $linentag($row) $m \
@@ -3149,7 +3230,7 @@ proc vrel_change {name ix op} {
     global highlight_related
 
     rhighlight_none
-    if {$highlight_related ne "None"} {
+    if {$highlight_related ne [mc "None"]} {
        run drawvisible
     }
 }
@@ -3163,7 +3244,7 @@ proc rhighlight_sel {a} {
     set desc_todo [list $a]
     catch {unset ancestor}
     set anc_todo [list $a]
-    if {$highlight_related ne "None"} {
+    if {$highlight_related ne [mc "None"]} {
        rhighlight_none
        run drawvisible
     }
@@ -3246,20 +3327,20 @@ proc askrelhighlight {row id} {
 
     if {![info exists selectedline]} return
     set isbold 0
-    if {$highlight_related eq "Descendent" ||
-       $highlight_related eq "Not descendent"} {
+    if {$highlight_related eq [mc "Descendent"] ||
+       $highlight_related eq [mc "Not descendent"]} {
        if {![info exists descendent($id)]} {
            is_descendent $id
        }
-       if {$descendent($id) == ($highlight_related eq "Descendent")} {
+       if {$descendent($id) == ($highlight_related eq [mc "Descendent"])} {
            set isbold 1
        }
-    } elseif {$highlight_related eq "Ancestor" ||
-             $highlight_related eq "Not ancestor"} {
+    } elseif {$highlight_related eq [mc "Ancestor"] ||
+             $highlight_related eq [mc "Not ancestor"]} {
        if {![info exists ancestor($id)]} {
            is_ancestor $id
        }
-       if {$ancestor($id) == ($highlight_related eq "Ancestor")} {
+       if {$ancestor($id) == ($highlight_related eq [mc "Ancestor"])} {
            set isbold 1
        }
     }
@@ -3319,17 +3400,12 @@ proc ordertoken {id} {
            set tok $ordertok($p)
            break
        }
-       if {[llength $children($curview,$p)] == 0} {
+       set id [first_real_child $curview,$p]
+       if {$id eq {}} {
            # it's a root
            set tok [lindex $varctok($curview) $a]
            break
        }
-       set id [lindex $children($curview,$p) 0]
-       if {$id eq $nullid || $id eq $nullid2} {
-           # XXX treat it as a root
-           set tok [lindex $varctok($curview) $a]
-           break
-       }
        if {[llength $parents($curview,$id)] == 1} {
            lappend todo [list $p {}]
        } else {
@@ -3494,7 +3570,7 @@ proc dodiffindex {} {
 }
 
 proc readdiffindex {fd serial} {
-    global mainheadid nullid2 curview commitinfo commitdata lserial
+    global mainheadid nullid nullid2 curview commitinfo commitdata lserial
 
     set isdiff 1
     if {[gets $fd line] < 0} {
@@ -3506,19 +3582,26 @@ proc readdiffindex {fd serial} {
     # we only need to see one line and we don't really care what it says...
     close $fd
 
-    # now see if there are any local changes not checked in to the index
-    if {$serial == $lserial} {
-       set fd [open "|git diff-files" r]
-       fconfigure $fd -blocking 0
-       filerun $fd [list readdifffiles $fd $serial]
+    if {$serial != $lserial} {
+       return 0
     }
 
-    if {$isdiff && $serial == $lserial && ![commitinview $nullid2 $curview]} {
+    # now see if there are any local changes not checked in to the index
+    set fd [open "|git diff-files" r]
+    fconfigure $fd -blocking 0
+    filerun $fd [list readdifffiles $fd $serial]
+
+    if {$isdiff && ![commitinview $nullid2 $curview]} {
        # add the line for the changes in the index to the graph
-       set hl "Local changes checked in to index but not committed"
+       set hl [mc "Local changes checked in to index but not committed"]
        set commitinfo($nullid2) [list  $hl {} {} {} {} "    $hl\n"]
        set commitdata($nullid2) "\n    $hl\n"
+       if {[commitinview $nullid $curview]} {
+           removerow $nullid $curview
+       }
        insertrow $nullid2 $mainheadid $curview
+    } elseif {!$isdiff && [commitinview $nullid2 $curview]} {
+       removerow $nullid2 $curview
     }
     return 0
 }
@@ -3537,9 +3620,13 @@ proc readdifffiles {fd serial} {
     # we only need to see one line and we don't really care what it says...
     close $fd
 
-    if {$isdiff && $serial == $lserial && ![commitinview $nullid $curview]} {
+    if {$serial != $lserial} {
+       return 0
+    }
+
+    if {$isdiff && ![commitinview $nullid $curview]} {
        # add the line for the local diff to the graph
-       set hl "Local uncommitted changes, not checked in to index"
+       set hl [mc "Local uncommitted changes, not checked in to index"]
        set commitinfo($nullid) [list  $hl {} {} {} {} "    $hl\n"]
        set commitdata($nullid) "\n    $hl\n"
        if {[commitinview $nullid2 $curview]} {
@@ -3548,6 +3635,8 @@ proc readdifffiles {fd serial} {
            set p $mainheadid
        }
        insertrow $nullid $p $curview
+    } elseif {!$isdiff && [commitinview $nullid $curview]} {
+       removerow $nullid $curview
     }
     return 0
 }
@@ -4373,7 +4462,7 @@ proc drawcmitrow {row} {
     if {$findpattern ne {} && ![info exists nhighlights($row)]} {
        askfindhighlight $row $id
     }
-    if {$highlight_related ne "None" && ![info exists rhighlights($row)]} {
+    if {$highlight_related ne [mc "None"] && ![info exists rhighlights($row)]} {
        askrelhighlight $row $id
     }
     if {![info exists iddrawn($id)]} {
@@ -4494,23 +4583,59 @@ proc undolayout {row} {
     }
 }
 
-proc drawfrac {f0 f1} {
-    global canv linespc
+proc drawvisible {} {
+    global canv linespc curview vrowmod selectedline targetrow targetid
+    global need_redisplay cscroll numcommits
 
+    set fs [$canv yview]
     set ymax [lindex [$canv cget -scrollregion] 3]
     if {$ymax eq {} || $ymax == 0} return
+    set f0 [lindex $fs 0]
+    set f1 [lindex $fs 1]
     set y0 [expr {int($f0 * $ymax)}]
-    set row [expr {int(($y0 - 3) / $linespc) - 1}]
     set y1 [expr {int($f1 * $ymax)}]
+
+    if {[info exists targetid]} {
+       if {[commitinview $targetid $curview]} {
+           set r [rowofcommit $targetid]
+           if {$r != $targetrow} {
+               # Fix up the scrollregion and change the scrolling position
+               # now that our target row has moved.
+               set diff [expr {($r - $targetrow) * $linespc}]
+               set targetrow $r
+               setcanvscroll
+               set ymax [lindex [$canv cget -scrollregion] 3]
+               incr y0 $diff
+               incr y1 $diff
+               set f0 [expr {$y0 / $ymax}]
+               set f1 [expr {$y1 / $ymax}]
+               allcanvs yview moveto $f0
+               $cscroll set $f0 $f1
+               set need_redisplay 1
+           }
+       } else {
+           unset targetid
+       }
+    }
+
+    set row [expr {int(($y0 - 3) / $linespc) - 1}]
     set endrow [expr {int(($y1 - 3) / $linespc) + 1}]
+    if {$endrow >= $vrowmod($curview)} {
+       update_arcrows $curview
+    }
+    if {[info exists selectedline] &&
+       $row <= $selectedline && $selectedline <= $endrow} {
+       set targetrow $selectedline
+    } else {
+       set targetrow [expr {int(($row + $endrow) / 2)}]
+    }
+    if {$targetrow >= $numcommits} {
+       set targetrow [expr {$numcommits - 1}]
+    }
+    set targetid [commitonrow $targetrow]
     drawcommits $row $endrow
 }
 
-proc drawvisible {} {
-    global canv
-    eval drawfrac [$canv yview]
-}
-
 proc clear_display {} {
     global iddrawn linesegs need_redisplay nrows_drawn
     global vhighlights fhighlights nhighlights rhighlights
@@ -4783,11 +4908,11 @@ proc notbusy {what} {
 
 proc findmatches {f} {
     global findtype findstring
-    if {$findtype == "Regexp"} {
+    if {$findtype == [mc "Regexp"]} {
        set matches [regexp -indices -all -inline $findstring $f]
     } else {
        set fs $findstring
-       if {$findtype == "IgnCase"} {
+       if {$findtype == [mc "IgnCase"]} {
            set f [string tolower $f]
            set fs [string tolower $fs]
        }
@@ -4818,8 +4943,8 @@ proc dofind {{dirn 1} {wrap 1}} {
        set findstartline $selectedline
     }
     set findcurline $findstartline
-    nowbusy finding "Searching"
-    if {$gdttype ne "containing:" && ![info exists filehighlight]} {
+    nowbusy finding [mc "Searching"]
+    if {$gdttype ne [mc "containing:"] && ![info exists filehighlight]} {
        after cancel do_file_hl $fh_serial
        do_file_hl $fh_serial
     }
@@ -4844,12 +4969,12 @@ proc findmore {} {
     global commitdata commitinfo numcommits findpattern findloc
     global findstartline findcurline findallowwrap
     global find_dirn gdttype fhighlights fprogcoord
-    global curview varcorder vrownum varccommits
+    global curview varcorder vrownum varccommits vrowmod
 
     if {![info exists find_dirn]} {
        return 0
     }
-    set fldtypes {Headline Author Date Committer CDate Comments}
+    set fldtypes [list [mc "Headline"] [mc "Author"] [mc "Date"] [mc "Committer"] [mc "CDate"] [mc "Comments"]]
     set l $findcurline
     set moretodo 0
     if {$find_dirn > 0} {
@@ -4880,6 +5005,9 @@ proc findmore {} {
        set n 500
        set moretodo 1
     }
+    if {$l + ($find_dirn > 0? $n: 1) > $vrowmod($curview)} {
+       update_arcrows $curview
+    }
     set found 0
     set domore 1
     set ai [bsearch $vrownum($curview) $l]
@@ -4887,7 +5015,7 @@ proc findmore {} {
     set arow [lindex $vrownum($curview) $ai]
     set ids [lindex $varccommits($curview,$a)]
     set arowend [expr {$arow + [llength $ids]}]
-    if {$gdttype eq "containing:"} {
+    if {$gdttype eq [mc "containing:"]} {
        for {} {$n > 0} {incr n -1; incr l $find_dirn} {
            if {$l < $arow || $l >= $arowend} {
                incr ai $find_dirn
@@ -4907,7 +5035,7 @@ proc findmore {} {
            }
            set info $commitinfo($id)
            foreach f $info ty $fldtypes {
-               if {($findloc eq "All fields" || $findloc eq $ty) &&
+               if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
                    [doesmatch $f]} {
                    set found 1
                    break
@@ -4926,14 +5054,18 @@ proc findmore {} {
            }
            set id [lindex $ids [expr {$l - $arow}]]
            if {![info exists fhighlights($l)]} {
+               # this sets fhighlights($l) to -1
                askfilehighlight $l $id
+           }
+           if {$fhighlights($l) > 0} {
+               set found $domore
+               break
+           }
+           if {$fhighlights($l) < 0} {
                if {$domore} {
                    set domore 0
                    set findcurline [expr {$l - $find_dirn}]
                }
-           } elseif {$fhighlights($l)} {
-               set found $domore
-               break
            }
        }
     }
@@ -4970,7 +5102,7 @@ proc findselectline {l} {
     set markingmatches 1
     set findcurline $l
     selectline $l 1
-    if {$findloc == "All fields" || $findloc == "Comments"} {
+    if {$findloc == [mc "All fields"] || $findloc == [mc "Comments"]} {
        # highlight the matches in the comments
        set f [$ctext get 1.0 $commentend]
        set matches [findmatches $f]
@@ -5027,7 +5159,9 @@ proc selcanvline {w x y} {
        set l 0
     }
     if {$w eq $canv} {
-       if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return
+       set xmax [lindex [$canv cget -scrollregion] 2]
+       set xleft [expr {[lindex [$canv xview] 0] * $xmax}]
+       if {![info exists rowtextx($l)] || $xleft + $x < $rowtextx($l)} return
     }
     unmarkmatches
     selectline $l 1
@@ -5274,13 +5408,12 @@ proc selectline {l isnew} {
 
     make_secsel $l
 
+    set id [commitonrow $l]
     if {$isnew} {
-       addtohistory [list selectline $l 0]
+       addtohistory [list selbyid $id]
     }
 
     set selectedline $l
-
-    set id [commitonrow $l]
     set currentid $id
     $sha1entry delete 0 end
     $sha1entry insert 0 $id
@@ -5293,11 +5426,11 @@ proc selectline {l isnew} {
     set linknum 0
     set info $commitinfo($id)
     set date [formatdate [lindex $info 2]]
-    $ctext insert end "Author: [lindex $info 1]  $date\n"
+    $ctext insert end "[mc "Author"]: [lindex $info 1]  $date\n"
     set date [formatdate [lindex $info 4]]
-    $ctext insert end "Committer: [lindex $info 3]  $date\n"
+    $ctext insert end "[mc "Committer"]: [lindex $info 3]  $date\n"
     if {[info exists idtags($id)]} {
-       $ctext insert end "Tags:"
+       $ctext insert end [mc "Tags:"]
        foreach tag $idtags($id) {
            $ctext insert end " $tag"
        }
@@ -5314,18 +5447,18 @@ proc selectline {l isnew} {
            } else {
                set tag m$np
            }
-           $ctext insert end "Parent: " $tag
+           $ctext insert end "[mc "Parent"]: " $tag
            appendwithlinks [commit_descriptor $p] {}
            incr np
        }
     } else {
        foreach p $olds {
-           append headers "Parent: [commit_descriptor $p]"
+           append headers "[mc "Parent"]: [commit_descriptor $p]"
        }
     }
 
     foreach c $children($curview,$id) {
-       append headers "Child:  [commit_descriptor $c]"
+       append headers "[mc "Child"]:  [commit_descriptor $c]"
     }
 
     # make anything that looks like a SHA1 ID be a clickable link
@@ -5334,13 +5467,13 @@ proc selectline {l isnew} {
        if {![info exists allcommits]} {
            getallcommits
        }
-       $ctext insert end "Branch: "
+       $ctext insert end "[mc "Branch"]: "
        $ctext mark set branch "end -1c"
        $ctext mark gravity branch left
-       $ctext insert end "\nFollows: "
+       $ctext insert end "\n[mc "Follows"]: "
        $ctext mark set follows "end -1c"
        $ctext mark gravity follows left
-       $ctext insert end "\nPrecedes: "
+       $ctext insert end "\n[mc "Precedes"]: "
        $ctext mark set precedes "end -1c"
        $ctext mark gravity precedes left
        $ctext insert end "\n"
@@ -5357,7 +5490,7 @@ proc selectline {l isnew} {
     $ctext conf -state disabled
     set commentend [$ctext index "end - 1c"]
 
-    init_flist "Comments"
+    init_flist [mc "Comments"]
     if {$cmitmode eq "tree"} {
        gettree $id
     } elseif {[llength $olds] <= 1} {
@@ -5622,7 +5755,7 @@ proc mergediff {id} {
        set cmd [concat $cmd -- $viewfiles($curview)]
     }
     if {[catch {set mdf [open $cmd r]} err]} {
-       error_popup "Error getting merge diffs: $err"
+       error_popup "[mc "Error getting merge diffs:"] $err"
        return
     }
     fconfigure $mdf -blocking 0
@@ -6299,9 +6432,9 @@ proc sha1change {n1 n2 op} {
     }
     if {[$sha1but cget -state] == $state} return
     if {$state == "normal"} {
-       $sha1but conf -state normal -relief raised -text "Goto: "
+       $sha1but conf -state normal -relief raised -text "[mc "Goto:"] "
     } else {
-       $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
+       $sha1but conf -state disabled -relief flat -text "[mc "SHA1 ID:"] "
     }
 }
 
@@ -6320,7 +6453,7 @@ proc gotocommit {} {
            set matches [array names varcid "$curview,$id*"]
            if {$matches ne {}} {
                if {[llength $matches] > 1} {
-                   error_popup "Short SHA1 id $id is ambiguous"
+                   error_popup [mc "Short SHA1 id %s is ambiguous" $id]
                    return
                }
                set id [lindex [split [lindex $matches 0] ","] 1]
@@ -6332,11 +6465,11 @@ proc gotocommit {} {
        return
     }
     if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
-       set type "SHA1 id"
+       set msg [mc "SHA1 id %s is not known" $sha1string]
     } else {
-       set type "Tag/Head"
+       set msg [mc "Tag/Head %s is not known" $sha1string]
     }
-    error_popup "$type $sha1string is not known"
+    error_popup $msg
 }
 
 proc lineenter {x y id} {
@@ -6466,17 +6599,17 @@ proc lineclick {x y id isnew} {
     $ctext conf -state normal
     clear_ctext
     settabs 0
-    $ctext insert end "Parent:\t"
+    $ctext insert end "[mc "Parent"]:\t"
     $ctext insert end $id link0
     setlink $id link0
     set info $commitinfo($id)
     $ctext insert end "\n\t[lindex $info 0]\n"
-    $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
+    $ctext insert end "\t[mc "Author"]:\t[lindex $info 1]\n"
     set date [formatdate [lindex $info 2]]
-    $ctext insert end "\tDate:\t$date\n"
+    $ctext insert end "\t[mc "Date"]:\t$date\n"
     set kids $children($curview,$id)
     if {$kids ne {}} {
-       $ctext insert end "\nChildren:"
+       $ctext insert end "\n[mc "Children"]:"
        set i 0
        foreach child $kids {
            incr i
@@ -6486,9 +6619,9 @@ proc lineclick {x y id isnew} {
            $ctext insert end $child link$i
            setlink $child link$i
            $ctext insert end "\n\t[lindex $info 0]"
-           $ctext insert end "\n\tAuthor:\t[lindex $info 1]"
+           $ctext insert end "\n\t[mc "Author"]:\t[lindex $info 1]"
            set date [formatdate [lindex $info 2]]
-           $ctext insert end "\n\tDate:\t$date\n"
+           $ctext insert end "\n\t[mc "Date"]:\t$date\n"
        }
     }
     $ctext conf -state disabled
@@ -6533,13 +6666,13 @@ proc rowmenu {x y id} {
     }
     if {$id ne $nullid && $id ne $nullid2} {
        set menu $rowctxmenu
-       $menu entryconfigure 7 -label "Reset $mainhead branch to here"
+       $menu entryconfigure 7 -label [mc "Reset %s branch to here" $mainhead]
     } else {
        set menu $fakerowmenu
     }
-    $menu entryconfigure "Diff this*" -state $state
-    $menu entryconfigure "Diff selected*" -state $state
-    $menu entryconfigure "Make patch" -state $state
+    $menu entryconfigure [mc "Diff this -> selected"] -state $state
+    $menu entryconfigure [mc "Diff selected -> this"] -state $state
+    $menu entryconfigure [mc "Make patch"] -state $state
     tk_popup $menu $x $y
 }
 
@@ -6564,13 +6697,13 @@ proc doseldiff {oldid newid} {
 
     $ctext conf -state normal
     clear_ctext
-    init_flist "Top"
-    $ctext insert end "From "
+    init_flist [mc "Top"]
+    $ctext insert end "[mc "From"] "
     $ctext insert end $oldid link0
     setlink $oldid link0
     $ctext insert end "\n     "
     $ctext insert end [lindex $commitinfo($oldid) 0]
-    $ctext insert end "\n\nTo   "
+    $ctext insert end "\n\n[mc "To"]   "
     $ctext insert end $newid link1
     setlink $newid link1
     $ctext insert end "\n     "
@@ -6593,9 +6726,9 @@ proc mkpatch {} {
     set patchtop $top
     catch {destroy $top}
     toplevel $top
-    label $top.title -text "Generate patch"
+    label $top.title -text [mc "Generate patch"]
     grid $top.title - -pady 10
-    label $top.from -text "From:"
+    label $top.from -text [mc "From:"]
     entry $top.fromsha1 -width 40 -relief flat
     $top.fromsha1 insert 0 $oldid
     $top.fromsha1 conf -state readonly
@@ -6604,7 +6737,7 @@ proc mkpatch {} {
     $top.fromhead insert 0 $oldhead
     $top.fromhead conf -state readonly
     grid x $top.fromhead -sticky w
-    label $top.to -text "To:"
+    label $top.to -text [mc "To:"]
     entry $top.tosha1 -width 40 -relief flat
     $top.tosha1 insert 0 $newid
     $top.tosha1 conf -state readonly
@@ -6613,16 +6746,16 @@ proc mkpatch {} {
     $top.tohead insert 0 $newhead
     $top.tohead conf -state readonly
     grid x $top.tohead -sticky w
-    button $top.rev -text "Reverse" -command mkpatchrev -padx 5
+    button $top.rev -text [mc "Reverse"] -command mkpatchrev -padx 5
     grid $top.rev x -pady 10
-    label $top.flab -text "Output file:"
+    label $top.flab -text [mc "Output file:"]
     entry $top.fname -width 60
     $top.fname insert 0 [file normalize "patch$patchnum.patch"]
     incr patchnum
     grid $top.flab $top.fname -sticky w
     frame $top.buts
-    button $top.buts.gen -text "Generate" -command mkpatchgo
-    button $top.buts.can -text "Cancel" -command mkpatchcan
+    button $top.buts.gen -text [mc "Generate"] -command mkpatchgo
+    button $top.buts.can -text [mc "Cancel"] -command 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
@@ -6657,7 +6790,7 @@ proc mkpatchgo {} {
     set cmd [lrange $cmd 1 end]
     lappend cmd >$fname &
     if {[catch {eval exec $cmd} err]} {
-       error_popup "Error creating patch: $err"
+       error_popup "[mc "Error creating patch:"] $err"
     }
     catch {destroy $patchtop}
     unset patchtop
@@ -6677,9 +6810,9 @@ proc mktag {} {
     set mktagtop $top
     catch {destroy $top}
     toplevel $top
-    label $top.title -text "Create tag"
+    label $top.title -text [mc "Create tag"]
     grid $top.title - -pady 10
-    label $top.id -text "ID:"
+    label $top.id -text [mc "ID:"]
     entry $top.sha1 -width 40 -relief flat
     $top.sha1 insert 0 $rowmenuid
     $top.sha1 conf -state readonly
@@ -6688,12 +6821,12 @@ proc mktag {} {
     $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
     $top.head conf -state readonly
     grid x $top.head -sticky w
-    label $top.tlab -text "Tag name:"
+    label $top.tlab -text [mc "Tag name:"]
     entry $top.tag -width 60
     grid $top.tlab $top.tag -sticky w
     frame $top.buts
-    button $top.buts.gen -text "Create" -command mktaggo
-    button $top.buts.can -text "Cancel" -command mktagcan
+    button $top.buts.gen -text [mc "Create"] -command mktaggo
+    button $top.buts.can -text [mc "Cancel"] -command 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
@@ -6707,11 +6840,11 @@ proc domktag {} {
     set id [$mktagtop.sha1 get]
     set tag [$mktagtop.tag get]
     if {$tag == {}} {
-       error_popup "No tag name specified"
+       error_popup [mc "No tag name specified"]
        return
     }
     if {[info exists tagids($tag)]} {
-       error_popup "Tag \"$tag\" already exists"
+       error_popup [mc "Tag \"%s\" already exists" $tag]
        return
     }
     if {[catch {
@@ -6721,7 +6854,7 @@ proc domktag {} {
        puts $f $id
        close $f
     } err]} {
-       error_popup "Error creating tag: $err"
+       error_popup "[mc "Error creating tag:"] $err"
        return
     }
 
@@ -6734,24 +6867,24 @@ proc domktag {} {
 }
 
 proc redrawtags {id} {
-    global canv linehtag idpos selectedline curview
+    global canv linehtag idpos currentid curview
     global canvxmax iddrawn
 
     if {![commitinview $id $curview]} return
     if {![info exists iddrawn($id)]} return
-    drawcommits [rowofcommit $id]
+    set row [rowofcommit $id]
     $canv delete tag.$id
     set xt [eval drawtags $id $idpos($id)]
-    $canv coords $linehtag([rowofcommit $id]) $xt [lindex $idpos($id) 2]
-    set text [$canv itemcget $linehtag([rowofcommit $id]) -text]
-    set xr [expr {$xt + [font measure mainfont $text]}]
+    $canv coords $linehtag($row) $xt [lindex $idpos($id) 2]
+    set text [$canv itemcget $linehtag($row) -text]
+    set font [$canv itemcget $linehtag($row) -font]
+    set xr [expr {$xt + [font measure $font $text]}]
     if {$xr > $canvxmax} {
        set canvxmax $xr
        setcanvscroll
     }
-    if {[info exists selectedline]
-       && $selectedline == [rowofcommit $id]} {
-       selectline $selectedline 0
+    if {[info exists currentid] && $currentid == $id} {
+       make_secsel $row
     }
 }
 
@@ -6774,9 +6907,9 @@ proc writecommit {} {
     set wrcomtop $top
     catch {destroy $top}
     toplevel $top
-    label $top.title -text "Write commit to file"
+    label $top.title -text [mc "Write commit to file"]
     grid $top.title - -pady 10
-    label $top.id -text "ID:"
+    label $top.id -text [mc "ID:"]
     entry $top.sha1 -width 40 -relief flat
     $top.sha1 insert 0 $rowmenuid
     $top.sha1 conf -state readonly
@@ -6785,16 +6918,16 @@ proc writecommit {} {
     $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
     $top.head conf -state readonly
     grid x $top.head -sticky w
-    label $top.clab -text "Command:"
+    label $top.clab -text [mc "Command:"]
     entry $top.cmd -width 60 -textvariable wrcomcmd
     grid $top.clab $top.cmd -sticky w -pady 10
-    label $top.flab -text "Output file:"
+    label $top.flab -text [mc "Output file:"]
     entry $top.fname -width 60
     $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
     grid $top.flab $top.fname -sticky w
     frame $top.buts
-    button $top.buts.gen -text "Write" -command wrcomgo
-    button $top.buts.can -text "Cancel" -command wrcomcan
+    button $top.buts.gen -text [mc "Write"] -command wrcomgo
+    button $top.buts.can -text [mc "Cancel"] -command 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
@@ -6809,7 +6942,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 "Error writing commit: $err"
+       error_popup "[mc "Error writing commit:"] $err"
     }
     catch {destroy $wrcomtop}
     unset wrcomtop
@@ -6828,19 +6961,19 @@ proc mkbranch {} {
     set top .makebranch
     catch {destroy $top}
     toplevel $top
-    label $top.title -text "Create new branch"
+    label $top.title -text [mc "Create new branch"]
     grid $top.title - -pady 10
-    label $top.id -text "ID:"
+    label $top.id -text [mc "ID:"]
     entry $top.sha1 -width 40 -relief flat
     $top.sha1 insert 0 $rowmenuid
     $top.sha1 conf -state readonly
     grid $top.id $top.sha1 -sticky w
-    label $top.nlab -text "Name:"
+    label $top.nlab -text [mc "Name:"]
     entry $top.name -width 40
     grid $top.nlab $top.name -sticky w
     frame $top.buts
-    button $top.buts.go -text "Create" -command [list mkbrgo $top]
-    button $top.buts.can -text "Cancel" -command "catch {destroy $top}"
+    button $top.buts.go -text [mc "Create"] -command [list mkbrgo $top]
+    button $top.buts.can -text [mc "Cancel"] -command "catch {destroy $top}"
     grid $top.buts.go $top.buts.can
     grid columnconfigure $top.buts 0 -weight 1 -uniform a
     grid columnconfigure $top.buts 1 -weight 1 -uniform a
@@ -6854,7 +6987,7 @@ proc mkbrgo {top} {
     set name [$top.name get]
     set id [$top.sha1 get]
     if {$name eq {}} {
-       error_popup "Please specify a name for the new branch"
+       error_popup [mc "Please specify a name for the new branch"]
        return
     }
     catch {destroy $top}
@@ -6883,11 +7016,12 @@ proc cherrypick {} {
     set oldhead [exec git rev-parse HEAD]
     set dheads [descheads $rowmenuid]
     if {$dheads ne {} && [lsearch -exact $dheads $oldhead] >= 0} {
-       set ok [confirm_popup "Commit [string range $rowmenuid 0 7] is already\
-                       included in branch $mainhead -- really re-apply it?"]
+       set ok [confirm_popup [mc "Commit %s is already\
+               included in branch %s -- really re-apply it?" \
+                                  [string range $rowmenuid 0 7] $mainhead]]
        if {!$ok} return
     }
-    nowbusy cherrypick "Cherry-picking"
+    nowbusy cherrypick [mc "Cherry-picking"]
     update
     # Unfortunately git-cherry-pick writes stuff to stderr even when
     # no error occurs, and exec takes that as an indication of error...
@@ -6899,7 +7033,7 @@ proc cherrypick {} {
     set newhead [exec git rev-parse HEAD]
     if {$newhead eq $oldhead} {
        notbusy cherrypick
-       error_popup "No changes committed"
+       error_popup [mc "No changes committed"]
        return
     }
     addnewchild $newhead $oldhead
@@ -6922,28 +7056,28 @@ proc resethead {} {
     set w ".confirmreset"
     toplevel $w
     wm transient $w .
-    wm title $w "Confirm reset"
+    wm title $w [mc "Confirm reset"]
     message $w.m -text \
-       "Reset branch $mainhead to [string range $rowmenuid 0 7]?" \
+       [mc "Reset branch %s to %s?" $mainhead [string range $rowmenuid 0 7]] \
        -justify center -aspect 1000
     pack $w.m -side top -fill x -padx 20 -pady 20
     frame $w.f -relief sunken -border 2
-    message $w.f.rt -text "Reset type:" -aspect 1000
+    message $w.f.rt -text [mc "Reset type:"] -aspect 1000
     grid $w.f.rt -sticky w
     set resettype mixed
     radiobutton $w.f.soft -value soft -variable resettype -justify left \
-       -text "Soft: Leave working tree and index untouched"
+       -text [mc "Soft: Leave working tree and index untouched"]
     grid $w.f.soft -sticky w
     radiobutton $w.f.mixed -value mixed -variable resettype -justify left \
-       -text "Mixed: Leave working tree untouched, reset index"
+       -text [mc "Mixed: Leave working tree untouched, reset index"]
     grid $w.f.mixed -sticky w
     radiobutton $w.f.hard -value hard -variable resettype -justify left \
-       -text "Hard: Reset working tree and index\n(discard ALL local changes)"
+       -text [mc "Hard: Reset working tree and index\n(discard ALL local changes)"]
     grid $w.f.hard -sticky w
     pack $w.f -side top -fill x
-    button $w.ok -text OK -command "set confirm_ok 1; destroy $w"
+    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 Cancel -command "destroy $w"
+    button $w.cancel -text [mc Cancel] -command "destroy $w"
     pack $w.cancel -side right -fill x -padx 20 -pady 20
     bind $w <Visibility> "grab $w; focus $w"
     tkwait window $w
@@ -6954,7 +7088,7 @@ proc resethead {} {
     } else {
        dohidelocalchanges
        filerun $fd [list readresetstat $fd]
-       nowbusy reset "Resetting"
+       nowbusy reset [mc "Resetting"]
     }
 }
 
@@ -7011,7 +7145,7 @@ proc cobranch {} {
 
     # check the tree is clean first??
     set oldmainhead $mainhead
-    nowbusy checkout "Checking out"
+    nowbusy checkout [mc "Checking out"]
     update
     dohidelocalchanges
     if {[catch {
@@ -7041,14 +7175,14 @@ proc rmbranch {} {
     set id $headmenuid
     # this check shouldn't be needed any more...
     if {$head eq $mainhead} {
-       error_popup "Cannot delete the currently checked-out branch"
+       error_popup [mc "Cannot delete the currently checked-out branch"]
        return
     }
     set dheads [descheads $id]
     if {[llength $dheads] == 1 && $idheads($dheads) eq $head} {
        # the stuff on this branch isn't on any other branch
-       if {![confirm_popup "The commits on branch $head aren't on any other\
-                       branch.\nReally delete branch $head?"]} return
+       if {![confirm_popup [mc "The commits on branch %s aren't on any other\
+                       branch.\nReally delete branch %s?" $head $head]]} return
     }
     nowbusy rmbranch
     update
@@ -7078,7 +7212,7 @@ proc showrefs {} {
        return
     }
     toplevel $top
-    wm title $top "Tags and heads: [file tail [pwd]]"
+    wm title $top [mc "Tags and heads: %s" [file tail [pwd]]]
     text $top.list -background $bgcolor -foreground $fgcolor \
        -selectbackground $selectbgcolor -font mainfont \
        -xscrollcommand "$top.xsb set" -yscrollcommand "$top.ysb set" \
@@ -7092,14 +7226,14 @@ proc showrefs {} {
     grid $top.list $top.ysb -sticky nsew
     grid $top.xsb x -sticky ew
     frame $top.f
-    label $top.f.l -text "Filter: " -font uifont
+    label $top.f.l -text "[mc "Filter"]: " -font uifont
     entry $top.f.e -width 20 -textvariable reflistfilter -font uifont
     set reflistfilter "*"
     trace add variable reflistfilter write reflistfilter_change
     pack $top.f.e -side right -fill x -expand 1
     pack $top.f.l -side left
     grid $top.f - -sticky ew -pady 2
-    button $top.close -command [list destroy $top] -text "Close" \
+    button $top.close -command [list destroy $top] -text [mc "Close"] \
        -font uifont
     grid $top.close -
     grid columnconfigure $top 0 -weight 1
@@ -7387,9 +7521,9 @@ proc getallclines {fd} {
            dropcache $err
            return
        }
-       error_popup "Error reading commit topology information;\
+       error_popup "[mc "Error reading commit topology information;\
                branch and preceding/following tag information\
-               will be incomplete.\n($err)"
+               will be incomplete."]\n($err)"
        set cacheok 0
     }
     if {[incr allcommits -1] == 0} {
@@ -8310,7 +8444,7 @@ proc changedrefs {} {
 }
 
 proc rereadrefs {} {
-    global idtags idheads idotherrefs mainhead
+    global idtags idheads idotherrefs mainheadid
 
     set refids [concat [array names idtags] \
                    [array names idheads] [array names idotherrefs]]
@@ -8319,7 +8453,7 @@ proc rereadrefs {} {
            set ref($id) [listrefs $id]
        }
     }
-    set oldmainhead $mainhead
+    set oldmainhead $mainheadid
     readrefs
     changedrefs
     set refids [lsort -unique [concat $refids [array names idtags] \
@@ -8327,8 +8461,8 @@ proc rereadrefs {} {
     foreach id $refids {
        set v [listrefs $id]
        if {![info exists ref($id)] || $ref($id) != $v ||
-           ($id eq $oldmainhead && $id ne $mainhead) ||
-           ($id eq $mainhead && $id ne $oldmainhead)} {
+           ($id eq $oldmainhead && $id ne $mainheadid) ||
+           ($id eq $mainheadid && $id ne $oldmainhead)} {
            redrawtags $id
        }
     }
@@ -8371,7 +8505,7 @@ proc showtag {tag isnew} {
     if {[info exists tagcontents($tag)]} {
        set text $tagcontents($tag)
     } else {
-       set text "Tag: $tag\nId:  $tagids($tag)"
+       set text "[mc "Tag"]: $tag\n[mc "Id"]:  $tagids($tag)"
     }
     appendwithlinks $text {}
     $ctext conf -state disabled
@@ -8411,7 +8545,7 @@ proc choosefont {font which} {
        font create sample
        eval font config sample [font actual $font]
        toplevel $top
-       wm title $top "Gitk font chooser"
+       wm title $top [mc "Gitk font chooser"]
        label $top.l -textvariable fontparam(which) -font uifont
        pack $top.l -side top
        set fontlist [lsort [font families]]
@@ -8428,10 +8562,10 @@ proc choosefont {font which} {
            -textvariable fontparam(size) \
            -validatecommand {string is integer -strict %s}
        checkbutton $top.g.bold -padx 5 \
-           -font {{Times New Roman} 12 bold} -text "B" -indicatoron 0 \
+           -font {{Times New Roman} 12 bold} -text [mc "B"] -indicatoron 0 \
            -variable fontparam(weight) -onvalue bold -offvalue normal
        checkbutton $top.g.ital -padx 5 \
-           -font {{Times New Roman} 12 italic} -text "I" -indicatoron 0  \
+           -font {{Times New Roman} 12 italic} -text [mc "I"] -indicatoron 0  \
            -variable fontparam(slant) -onvalue italic -offvalue roman
        pack $top.g.size $top.g.bold $top.g.ital -side left
        pack $top.g -side top
@@ -8442,9 +8576,9 @@ proc choosefont {font which} {
        bind $top.c <Configure> [list centertext $top.c]
        pack $top.c -side top -fill x
        frame $top.buts
-       button $top.buts.ok -text "OK" -command fontok -default active \
+       button $top.buts.ok -text [mc "OK"] -command fontok -default active \
            -font uifont
-       button $top.buts.can -text "Cancel" -command fontcan -default normal \
+       button $top.buts.can -text [mc "Cancel"] -command fontcan -default normal \
            -font uifont
        grid $top.buts.ok $top.buts.can
        grid columnconfigure $top.buts 0 -weight 1 -uniform a
@@ -8526,85 +8660,85 @@ proc doprefs {} {
        set oldprefs($v) [set $v]
     }
     toplevel $top
-    wm title $top "Gitk preferences"
-    label $top.ldisp -text "Commit list display options"
+    wm title $top [mc "Gitk preferences"]
+    label $top.ldisp -text [mc "Commit list display options"]
     $top.ldisp configure -font uifont
     grid $top.ldisp - -sticky w -pady 10
     label $top.spacer -text " "
-    label $top.maxwidthl -text "Maximum graph width (lines)" \
+    label $top.maxwidthl -text [mc "Maximum graph width (lines)"] \
        -font optionfont
     spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
     grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w
-    label $top.maxpctl -text "Maximum graph width (% of pane)" \
+    label $top.maxpctl -text [mc "Maximum graph width (% of pane)"] \
        -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 "Show local changes" -font optionfont
+    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
     grid x $top.showlocal -sticky w
 
-    label $top.ddisp -text "Diff display options"
+    label $top.ddisp -text [mc "Diff display options"]
     $top.ddisp configure -font uifont
     grid $top.ddisp - -sticky w -pady 10
-    label $top.tabstopl -text "Tab spacing" -font optionfont
+    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 "Display nearby tags" -font optionfont
+    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
     grid x $top.ntag -sticky w
     frame $top.ldiff
-    label $top.ldiff.l -text "Limit diffs to listed paths" -font optionfont
+    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
     grid x $top.ldiff -sticky w
 
-    label $top.cdisp -text "Colors: press to choose"
+    label $top.cdisp -text [mc "Colors: press to choose"]
     $top.cdisp configure -font uifont
     grid $top.cdisp - -sticky w -pady 10
     label $top.bg -padx 40 -relief sunk -background $bgcolor
-    button $top.bgbut -text "Background" -font optionfont \
+    button $top.bgbut -text [mc "Background"] -font optionfont \
        -command [list choosecolor bgcolor 0 $top.bg background setbg]
     grid x $top.bgbut $top.bg -sticky w
     label $top.fg -padx 40 -relief sunk -background $fgcolor
-    button $top.fgbut -text "Foreground" -font optionfont \
+    button $top.fgbut -text [mc "Foreground"] -font optionfont \
        -command [list choosecolor fgcolor 0 $top.fg foreground setfg]
     grid x $top.fgbut $top.fg -sticky w
     label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
-    button $top.diffoldbut -text "Diff: old lines" -font optionfont \
+    button $top.diffoldbut -text [mc "Diff: old lines"] -font optionfont \
        -command [list choosecolor diffcolors 0 $top.diffold "diff old lines" \
                      [list $ctext tag conf d0 -foreground]]
     grid x $top.diffoldbut $top.diffold -sticky w
     label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
-    button $top.diffnewbut -text "Diff: new lines" -font optionfont \
+    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]]
     grid x $top.diffnewbut $top.diffnew -sticky w
     label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
-    button $top.hunksepbut -text "Diff: hunk header" -font optionfont \
+    button $top.hunksepbut -text [mc "Diff: hunk header"] -font optionfont \
        -command [list choosecolor diffcolors 2 $top.hunksep \
                      "diff hunk header" \
                      [list $ctext tag conf hunksep -foreground]]
     grid x $top.hunksepbut $top.hunksep -sticky w
     label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
-    button $top.selbgbut -text "Select bg" -font optionfont \
+    button $top.selbgbut -text [mc "Select bg"] -font optionfont \
        -command [list choosecolor selectbgcolor 0 $top.selbgsep background setselbg]
     grid x $top.selbgbut $top.selbgsep -sticky w
 
-    label $top.cfont -text "Fonts: press to choose"
+    label $top.cfont -text [mc "Fonts: press to choose"]
     $top.cfont configure -font uifont
     grid $top.cfont - -sticky w -pady 10
-    mkfontdisp mainfont $top "Main font"
-    mkfontdisp textfont $top "Diff display font"
-    mkfontdisp uifont $top "User interface font"
+    mkfontdisp mainfont $top [mc "Main font"]
+    mkfontdisp textfont $top [mc "Diff display font"]
+    mkfontdisp uifont $top [mc "User interface font"]
 
     frame $top.buts
-    button $top.buts.ok -text "OK" -command prefsok -default active
+    button $top.buts.ok -text [mc "OK"] -command prefsok -default active
     $top.buts.ok configure -font uifont
-    button $top.buts.can -text "Cancel" -command prefscan -default normal
+    button $top.buts.can -text [mc "Cancel"] -command prefscan -default normal
     $top.buts.can configure -font uifont
     grid $top.buts.ok $top.buts.can
     grid columnconfigure $top.buts 0 -weight 1 -uniform a
@@ -8617,7 +8751,7 @@ proc choosecolor {v vi w x cmd} {
     global $v
 
     set c [tk_chooseColor -initialcolor [lindex [set $v] $vi] \
-              -title "Gitk: choose color for $x"]
+              -title [mc "Gitk: choose color for %s" $x]]
     if {$c eq {}} return
     $w conf -background $c
     lset $v $vi $c
@@ -8998,8 +9132,8 @@ proc tcl_encoding {enc} {
 
 # First check that Tcl/Tk is recent enough
 if {[catch {package require Tk 8.4} err]} {
-    show_error {} . "Sorry, gitk cannot run with this version of Tcl/Tk.\n\
-                    Gitk requires at least Tcl/Tk 8.4."
+    show_error {} . [mc "Sorry, gitk cannot run with this version of Tcl/Tk.\n\
+                    Gitk requires at least Tcl/Tk 8.4."]
     exit 1
 }
 
@@ -9047,6 +9181,25 @@ set diffcolors {red "#00a000" blue}
 set diffcontext 3
 set selectbgcolor gray85
 
+## For msgcat loading, first locate the installation location.
+if { [info exists ::env(GITK_MSGSDIR)] } {
+    ## Msgsdir was manually set in the environment.
+    set gitk_msgsdir $::env(GITK_MSGSDIR)
+} else {
+    ## Let's guess the prefix from argv0.
+    set gitk_prefix [file dirname [file dirname [file normalize $argv0]]]
+    set gitk_libdir [file join $gitk_prefix share gitk lib]
+    set gitk_msgsdir [file join $gitk_libdir msgs]
+    unset gitk_prefix
+}
+
+## Internationalization (i18n) through msgcat and gettext. See
+## http://www.gnu.org/software/gettext/manual/html_node/Tcl.html
+package require msgcat
+namespace import ::msgcat::mc
+## And eventually load the actual message catalog
+::msgcat::mcload $gitk_msgsdir
+
 catch {source ~/.gitk}
 
 font create optionfont -family sans-serif -size -12
@@ -9064,11 +9217,11 @@ eval font create uifont [fontflags uifont]
 
 # check that we can find a .git directory somewhere...
 if {[catch {set gitdir [gitdir]}]} {
-    show_error {} . "Cannot find a git repository here."
+    show_error {} . [mc "Cannot find a git repository here."]
     exit 1
 }
 if {![file isdirectory $gitdir]} {
-    show_error {} . "Cannot find the git directory \"$gitdir\"."
+    show_error {} . [mc "Cannot find the git directory \"%s\"." $gitdir]
     exit 1
 }
 
@@ -9107,8 +9260,8 @@ if {$i >= [llength $argv] && $revtreeargs ne {}} {
        # with git log and git rev-list, check revtreeargs for filenames.
        foreach arg $revtreeargs {
            if {[file exists $arg]} {
-               show_error {} . "Ambiguous argument '$arg': both revision\
-                                and filename"
+               show_error {} . [mc "Ambiguous argument '%s': both revision\
+                                and filename" $arg]
                exit 1
            }
        }
@@ -9119,7 +9272,7 @@ if {$i >= [llength $argv] && $revtreeargs ne {}} {
        if {$i > 0} {
            set err [string range $err [expr {$i + 6}] end]
        }
-       show_error {} . "Bad arguments to gitk:\n$err"
+       show_error {} . "[mc "Bad arguments to gitk:"]\n$err"
        exit 1
     }
 }
@@ -9131,7 +9284,7 @@ if {$mergeonly} {
     if {[catch {
        set fd [open "| git ls-files -u" r]
     } err]} {
-       show_error {} . "Couldn't get list of unmerged files: $err"
+       show_error {} . "[mc "Couldn't get list of unmerged files:"] $err"
        exit 1
     }
     while {[gets $fd line] >= 0} {
@@ -9147,11 +9300,11 @@ if {$mergeonly} {
     catch {close $fd}
     if {$mlist eq {}} {
        if {$nr_unmerged == 0} {
-           show_error {} . "No files selected: --merge specified but\
-                            no files are unmerged."
+           show_error {} . [mc "No files selected: --merge specified but\
+                            no files are unmerged."]
        } else {
-           show_error {} . "No files selected: --merge specified but\
-                            no unmerged files are within file limit."
+           show_error {} . [mc "No files selected: --merge specified but\
+                            no unmerged files are within file limit."]
        }
        exit 1
     }
@@ -9183,15 +9336,14 @@ set firsttabstop 0
 set nextviewnum 1
 set curview 0
 set selectedview 0
-set selectedhlview None
-set highlight_related None
+set selectedhlview [mc "None"]
+set highlight_related [mc "None"]
 set highlight_files {}
 set viewfiles(0) {}
 set viewperm(0) 0
 set viewargs(0) {}
 
 set loginstance 0
-set getdbg 0
 set cmdlineok 0
 set stopped 0
 set stuffsaved 0
@@ -9209,13 +9361,13 @@ if {$cmdline_files ne {} || $revtreeargs ne {}} {
     set curview 1
     set selectedview 1
     set nextviewnum 2
-    set viewname(1) "Command line"
+    set viewname(1) [mc "Command line"]
     set viewfiles(1) $cmdline_files
     set viewargs(1) $revtreeargs
     set viewperm(1) 0
     addviewmenu 1
-    .bar.view entryconf Edit* -state normal
-    .bar.view entryconf Delete* -state normal
+    .bar.view entryconf [mc "Edit view..."] -state normal
+    .bar.view entryconf [mc "Delete view"] -state normal
 }
 
 if {[info exists permviews]} {