gitk: Allow starting gui blame for a specific line
[gitweb.git] / gitk
diff --git a/gitk b/gitk
index 00ea8da7359cb3fa8197adce0a47d7a0c5c54a92..00037976970d57888e2db09eec77d836b2215bbb 100755 (executable)
--- a/gitk
+++ b/gitk
@@ -307,7 +307,7 @@ proc start_rev_list {view} {
     global startmsecs commitidx viewcomplete curview
     global tclencoding
     global viewargs viewargscmd viewfiles vfilelimit
-    global showlocalchanges commitinterest
+    global showlocalchanges
     global viewactive viewinstances vmergeonly
     global mainheadid
     global vcanopt vflags vrevs vorigargs
@@ -368,7 +368,7 @@ proc start_rev_list {view} {
     set i [reg_instance $fd]
     set viewinstances($view) [list $i]
     if {$showlocalchanges && $mainheadid ne {}} {
-       lappend commitinterest($mainheadid) {dodiffindex}
+       interestedin $mainheadid dodiffindex
     }
     fconfigure $fd -blocking 0 -translation lf -eofchar {}
     if {$tclencoding != {}} {
@@ -1231,7 +1231,7 @@ proc commitonrow {row} {
 
 proc closevarcs {v} {
     global varctok varccommits varcid parents children
-    global cmitlisted commitidx commitinterest vtokmod
+    global cmitlisted commitidx vtokmod
 
     set missing_parents 0
     set scripts {}
@@ -1256,12 +1256,7 @@ proc closevarcs {v} {
            }
            lappend varccommits($v,$b) $p
            incr commitidx($v)
-           if {[info exists commitinterest($p)]} {
-               foreach script $commitinterest($p) {
-                   lappend scripts [string map [list "%I" $p] $script]
-               }
-               unset commitinterest($id)
-           }
+           set scripts [check_interest $p $scripts]
        }
     }
     if {$missing_parents > 0} {
@@ -1297,8 +1292,41 @@ proc rewrite_commit {v id rwid} {
     }
 }
 
+# Mechanism for registering a command to be executed when we come
+# across a particular commit.  To handle the case when only the
+# prefix of the commit is known, the commitinterest array is now
+# indexed by the first 4 characters of the ID.  Each element is a
+# list of id, cmd pairs.
+proc interestedin {id cmd} {
+    global commitinterest
+
+    lappend commitinterest([string range $id 0 3]) $id $cmd
+}
+
+proc check_interest {id scripts} {
+    global commitinterest
+
+    set prefix [string range $id 0 3]
+    if {[info exists commitinterest($prefix)]} {
+       set newlist {}
+       foreach {i script} $commitinterest($prefix) {
+           if {[string match "$i*" $id]} {
+               lappend scripts [string map [list "%I" $id "%P" $i] $script]
+           } else {
+               lappend newlist $i $script
+           }
+       }
+       if {$newlist ne {}} {
+           set commitinterest($prefix) $newlist
+       } else {
+           unset commitinterest($prefix)
+       }
+    }
+    return $scripts
+}
+
 proc getcommitlines {fd inst view updating}  {
-    global cmitlisted commitinterest leftover
+    global cmitlisted leftover
     global commitidx commitdata vdatemode
     global parents children curview hlview
     global idpending ordertok
@@ -1474,12 +1502,7 @@ proc getcommitlines {fd inst view updating}  {
            incr i
        }
 
-       if {[info exists commitinterest($id)]} {
-           foreach script $commitinterest($id) {
-               lappend scripts [string map [list "%I" $id] $script]
-           }
-           unset commitinterest($id)
-       }
+       set scripts [check_interest $id $scripts]
        set gotsome 1
     }
     if {$gotsome} {
@@ -1608,6 +1631,19 @@ proc getcommit {id} {
     return 1
 }
 
+# Expand an abbreviated commit ID to a list of full 40-char IDs that match
+# and are present in the current view.
+# This is fairly slow...
+proc longid {prefix} {
+    global varcid curview
+
+    set ids {}
+    foreach match [array names varcid "$curview,$prefix*"] {
+       lappend ids [lindex [split $match ","] 1]
+    }
+    return $ids
+}
+
 proc readrefs {} {
     global tagids idtags headids idheads tagobjid
     global otherrefids idotherrefs mainhead mainheadid
@@ -1750,6 +1786,53 @@ proc setoptions {} {
     option add *Entry.font uifont startupFile
 }
 
+# Make a menu and submenus.
+# m is the window name for the menu, items is the list of menu items to add.
+# Each item is a list {mc label type description options...}
+# mc is ignored; it's so we can put mc there to alert xgettext
+# label is the string that appears in the menu
+# type is cascade, command or radiobutton (should add checkbutton)
+# description depends on type; it's the sublist for cascade, the
+# command to invoke for command, or {variable value} for radiobutton
+proc makemenu {m items} {
+    menu $m
+    foreach i $items {
+       set name [mc [lindex $i 1]]
+       set type [lindex $i 2]
+       set thing [lindex $i 3]
+       set params [list $type]
+       if {$name ne {}} {
+           set u [string first "&" [string map {&& x} $name]]
+           lappend params -label [string map {&& & & {}} $name]
+           if {$u >= 0} {
+               lappend params -underline $u
+           }
+       }
+       switch -- $type {
+           "cascade" {
+               set submenu [string tolower [string map {& ""} [lindex $i 1]]]
+               lappend params -menu $m.$submenu
+           }
+           "command" {
+               lappend params -command $thing
+           }
+           "radiobutton" {
+               lappend params -variable [lindex $thing 0] \
+                   -value [lindex $thing 1]
+           }
+       }
+       eval $m add $params [lrange $i 4 end]
+       if {$type eq "cascade"} {
+           makemenu $m.$submenu $thing
+       }
+    }
+}
+
+# translate string and remove ampersands
+proc mca {str} {
+    return [string map {&& & & {}} [mc $str]]
+}
+
 proc makewindow {} {
     global canv canv2 canv3 linespc charspc ctext cflist cscroll
     global tabstop
@@ -1767,33 +1850,31 @@ proc makewindow {} {
     global rprogitem rprogcoord rownumsel numcommits
     global have_tk85
 
-    menu .bar
-    .bar add cascade -label [mc "File"] -menu .bar.file
-    menu .bar.file
-    .bar.file add command -label [mc "Update"] -command updatecommits
-    .bar.file add command -label [mc "Reload"] -command reloadcommits
-    .bar.file add command -label [mc "Reread references"] -command rereadrefs
-    .bar.file add command -label [mc "List references"] -command showrefs
-    .bar.file add command -label [mc "Quit"] -command doquit
-    menu .bar.edit
-    .bar add cascade -label [mc "Edit"] -menu .bar.edit
-    .bar.edit add command -label [mc "Preferences"] -command doprefs
-
-    menu .bar.view
-    .bar add cascade -label [mc "View"] -menu .bar.view
-    .bar.view add command -label [mc "New view..."] -command {newview 0}
-    .bar.view add command -label [mc "Edit view..."] -command editview \
-       -state disabled
-    .bar.view add command -label [mc "Delete view"] -command delview -state disabled
-    .bar.view add separator
-    .bar.view add radiobutton -label [mc "All files"] -command {showview 0} \
-       -variable selectedview -value 0
-
-    menu .bar.help
-    .bar add cascade -label [mc "Help"] -menu .bar.help
-    .bar.help add command -label [mc "About gitk"] -command about
-    .bar.help add command -label [mc "Key bindings"] -command keys
-    .bar.help configure
+    # The "mc" arguments here are purely so that xgettext
+    # sees the following string as needing to be translated
+    makemenu .bar {
+       {mc "File" cascade {
+           {mc "Update" command updatecommits -accelerator F5}
+           {mc "Reload" command reloadcommits}
+           {mc "Reread references" command rereadrefs}
+           {mc "List references" command showrefs}
+           {mc "Quit" command doquit}
+       }}
+       {mc "Edit" cascade {
+           {mc "Preferences" command doprefs}
+       }}
+       {mc "View" cascade {
+           {mc "New view..." command {newview 0}}
+           {mc "Edit view..." command editview -state disabled}
+           {mc "Delete view" command delview -state disabled}
+           {xx "" separator}
+           {mc "All files" radiobutton {selectedview 0} -command {showview 0}}
+       }}
+       {mc "Help" cascade {
+           {mc "About gitk" command about}
+           {mc "Key bindings" command keys}
+       }}
+    }
     . configure -menu .bar
 
     # the gui has upper and lower half, parts of a paned window.
@@ -2168,55 +2249,56 @@ proc makewindow {} {
     bind $cflist <ButtonRelease-1> {treeclick %W %x %y}
     global ctxbut
     bind $cflist $ctxbut {pop_flist_menu %W %X %Y %x %y}
+    bind $ctext $ctxbut {pop_diff_menu %W %X %Y %x %y}
 
     set maincursor [. cget -cursor]
     set textcursor [$ctext cget -cursor]
     set curtextcursor $textcursor
 
     set rowctxmenu .rowctxmenu
-    menu $rowctxmenu -tearoff 0
-    $rowctxmenu add command -label [mc "Diff this -> selected"] \
-       -command {diffvssel 0}
-    $rowctxmenu add command -label [mc "Diff selected -> this"] \
-       -command {diffvssel 1}
-    $rowctxmenu add command -label [mc "Make patch"] -command mkpatch
-    $rowctxmenu add command -label [mc "Create tag"] -command mktag
-    $rowctxmenu add command -label [mc "Write commit to file"] -command writecommit
-    $rowctxmenu add command -label [mc "Create new branch"] -command mkbranch
-    $rowctxmenu add command -label [mc "Cherry-pick this commit"] \
-       -command cherrypick
-    $rowctxmenu add command -label [mc "Reset HEAD branch to here"] \
-       -command resethead
+    makemenu $rowctxmenu {
+       {mc "Diff this -> selected" command {diffvssel 0}}
+       {mc "Diff selected -> this" command {diffvssel 1}}
+       {mc "Make patch" command mkpatch}
+       {mc "Create tag" command mktag}
+       {mc "Write commit to file" command writecommit}
+       {mc "Create new branch" command mkbranch}
+       {mc "Cherry-pick this commit" command cherrypick}
+       {mc "Reset HEAD branch to here" command resethead}
+    }
+    $rowctxmenu configure -tearoff 0
 
     set fakerowmenu .fakerowmenu
-    menu $fakerowmenu -tearoff 0
-    $fakerowmenu add command -label [mc "Diff this -> selected"] \
-       -command {diffvssel 0}
-    $fakerowmenu add command -label [mc "Diff selected -> this"] \
-       -command {diffvssel 1}
-    $fakerowmenu add command -label [mc "Make patch"] -command mkpatch
-#    $fakerowmenu add command -label [mc "Commit"] -command {mkcommit 0}
-#    $fakerowmenu add command -label [mc "Commit all"] -command {mkcommit 1}
-#    $fakerowmenu add command -label [mc "Revert local changes"] -command revertlocal
+    makemenu $fakerowmenu {
+       {mc "Diff this -> selected" command {diffvssel 0}}
+       {mc "Diff selected -> this" command {diffvssel 1}}
+       {mc "Make patch" command mkpatch}
+    }
+    $fakerowmenu configure -tearoff 0
 
     set headctxmenu .headctxmenu
-    menu $headctxmenu -tearoff 0
-    $headctxmenu add command -label [mc "Check out this branch"] \
-       -command cobranch
-    $headctxmenu add command -label [mc "Remove this branch"] \
-       -command rmbranch
+    makemenu $headctxmenu {
+       {mc "Check out this branch" command cobranch}
+       {mc "Remove this branch" command rmbranch}
+    }
+    $headctxmenu configure -tearoff 0
 
     global flist_menu
     set flist_menu .flistctxmenu
-    menu $flist_menu -tearoff 0
-    $flist_menu add command -label [mc "Highlight this too"] \
-       -command {flist_hl 0}
-    $flist_menu add command -label [mc "Highlight this only"] \
-       -command {flist_hl 1}
-    $flist_menu add command -label [mc "External diff"] \
-        -command {external_diff}
-    $flist_menu add command -label [mc "Blame parent commit"] \
-        -command {external_blame 1}
+    makemenu $flist_menu {
+       {mc "Highlight this too" command {flist_hl 0}}
+       {mc "Highlight this only" command {flist_hl 1}}
+       {mc "External diff" command {external_diff}}
+       {mc "Blame parent commit" command {external_blame 1}}
+    }
+    $flist_menu configure -tearoff 0
+
+    global diff_menu
+    set diff_menu .diffctxmenu
+    makemenu $diff_menu {
+       {mc "Run git gui blame on this line" command {external_blame_diff}}
+    }
+    $diff_menu configure -tearoff 0
 }
 
 # Windows sends all mouse wheel events to the current focused window, not
@@ -2919,6 +3001,34 @@ proc pop_flist_menu {w X Y x y} {
     tk_popup $flist_menu $X $Y
 }
 
+proc find_ctext_fileinfo {line} {
+    global ctext_file_names ctext_file_lines
+
+    set ok [bsearch $ctext_file_lines $line]
+    set tline [lindex $ctext_file_lines $ok]
+
+    if {$ok >= [llength $ctext_file_lines] || $line < $tline} {
+        return {}
+    } else {
+        return [list [lindex $ctext_file_names $ok] $tline]
+    }
+}
+
+proc pop_diff_menu {w X Y x y} {
+    global ctext diff_menu flist_menu_file
+    global diff_menu_txtpos diff_menu_line
+    global diff_menu_filebase
+
+    stopfinding
+    set diff_menu_txtpos [split [$w index "@$x,$y"] "."]
+    set diff_menu_line [lindex $diff_menu_txtpos 0]
+    set f [find_ctext_fileinfo $diff_menu_line]
+    if {$f eq {}} return
+    set flist_menu_file [lindex $f 0]
+    set diff_menu_filebase [lindex $f 1]
+    tk_popup $diff_menu $X $Y
+}
+
 proc flist_hl {only} {
     global flist_menu_file findstring gdttype
 
@@ -3025,7 +3135,96 @@ proc external_diff {} {
     }
 }
 
-proc external_blame {parent_idx} {
+proc find_hunk_blamespec {base line} {
+    global ctext
+
+    # Find and parse the hunk header
+    set s_lix [$ctext search -backwards -regexp ^@@ "$line.0 lineend" $base.0]
+    if {$s_lix eq {}} return
+
+    set s_line [$ctext get $s_lix "$s_lix + 1 lines"]
+    if {![regexp {^@@@*(( -\d+(,\d+)?)+) \+(\d+)(,\d+)? @@} $s_line \
+           s_line old_specs osz osz1 new_line nsz]} {
+       return
+    }
+
+    # base lines for the parents
+    set base_lines [list $new_line]
+    foreach old_spec [lrange [split $old_specs " "] 1 end] {
+       if {![regexp -- {-(\d+)(,\d+)?} $old_spec \
+               old_spec old_line osz]} {
+           return
+       }
+       lappend base_lines $old_line
+    }
+
+    # Now scan the lines to determine offset within the hunk
+    set parent {}
+    set max_parent [expr {[llength $base_lines]-2}]
+    set dline 0
+    set s_lno [lindex [split $s_lix "."] 0]
+
+    for {set i $line} {$i > $s_lno} {incr i -1} {
+       set c_line [$ctext get $i.0 "$i.0 + 1 lines"]
+       # Determine if the line is removed
+       set chunk [string range $c_line 0 $max_parent]
+       set removed_idx [string first "-" $chunk]
+       # Choose a parent index
+       if {$parent eq {}} {
+           if {$removed_idx >= 0} {
+               set parent $removed_idx
+           } else {
+               set unchanged_idx [string first " " $chunk]
+               if {$unchanged_idx >= 0} {
+                   set parent $unchanged_idx
+               } else {
+                   # blame the current commit
+                   set parent -1
+               }
+           }
+       }
+       # then count other lines that belong to it
+       if {$parent >= 0} {
+           set code [string index $c_line $parent]
+           if {$code eq "-" || ($removed_idx < 0 && $code ne "+")} {
+               incr dline
+           }
+       } else {
+           if {$removed_idx < 0} {
+               incr dline
+           }
+       }
+    }
+
+    if {$parent eq {}} { set parent -1 }
+    incr parent
+    incr dline [lindex $base_lines $parent]
+    return [list $parent $dline]
+}
+
+proc external_blame_diff {} {
+    global currentid diffmergeid cmitmode
+    global diff_menu_txtpos diff_menu_line
+    global diff_menu_filebase flist_menu_file
+
+    if {$cmitmode eq "tree"} {
+       set parent_idx 0
+       set line [expr {$diff_menu_line - $diff_menu_filebase - 1}]
+    } else {
+       set hinfo [find_hunk_blamespec $diff_menu_filebase $diff_menu_line]
+       if {$hinfo ne {}} {
+           set parent_idx [lindex $hinfo 0]
+           set line [lindex $hinfo 1]
+       } else {
+           set parent_idx 0
+           set line 0
+       }
+    }
+
+    external_blame $parent_idx $line
+}
+
+proc external_blame {parent_idx {line {}}} {
     global flist_menu_file
     global nullid nullid2
     global parentlist selectedline currentid
@@ -3041,7 +3240,12 @@ proc external_blame {parent_idx} {
        return
     }
 
-    if {[catch {exec git gui blame $base_commit $flist_menu_file &} err]} {
+    set cmdline [list git gui blame]
+    if {$line ne {} && $line > 1} {
+       lappend cmdline "--line=$line"
+    }
+    lappend cmdline $base_commit $flist_menu_file
+    if {[catch {eval exec $cmdline &} err]} {
        error_popup "[mc "git gui blame: command failed:"] $err"
     }
 }
@@ -3376,8 +3580,8 @@ proc showview {n} {
 
     set curview $n
     set selectedview $n
-    .bar.view entryconf [mc "Edit view..."] -state [expr {$n == 0? "disabled": "normal"}]
-    .bar.view entryconf [mc "Delete view"] -state [expr {$n == 0? "disabled": "normal"}]
+    .bar.view entryconf [mca "Edit view..."] -state [expr {$n == 0? "disabled": "normal"}]
+    .bar.view entryconf [mca "Delete view"] -state [expr {$n == 0? "disabled": "normal"}]
 
     run refill_reflist
     if {![info exists viewcomplete($n)]} {
@@ -4081,7 +4285,7 @@ proc visiblerows {} {
 proc layoutmore {} {
     global commitidx viewcomplete curview
     global numcommits pending_select curview
-    global lastscrollset lastscrollrows commitinterest
+    global lastscrollset lastscrollrows
 
     if {$lastscrollrows < 100 || $viewcomplete($curview) ||
        [clock clicks -milliseconds] - $lastscrollset > 500} {
@@ -4102,7 +4306,7 @@ proc doshowlocalchanges {} {
     if {[commitinview $mainheadid $curview]} {
        dodiffindex
     } else {
-       lappend commitinterest($mainheadid) {dodiffindex}
+       interestedin $mainheadid dodiffindex
     }
 }
 
@@ -5757,11 +5961,11 @@ proc commit_descriptor {p} {
 # append some text to the ctext widget, and make any SHA1 ID
 # that we know about be a clickable link.
 proc appendwithlinks {text tags} {
-    global ctext linknum curview pendinglinks
+    global ctext linknum curview
 
     set start [$ctext index "end - 1c"]
     $ctext insert end $text $tags
-    set links [regexp -indices -all -inline {[0-9a-f]{40}} $text]
+    set links [regexp -indices -all -inline {\m[0-9a-f]{6,40}\M} $text]
     foreach l $links {
        set s [lindex $l 0]
        set e [lindex $l 1]
@@ -5775,16 +5979,27 @@ proc appendwithlinks {text tags} {
 }
 
 proc setlink {id lk} {
-    global curview ctext pendinglinks commitinterest
+    global curview ctext pendinglinks
 
-    if {[commitinview $id $curview]} {
+    set known 0
+    if {[string length $id] < 40} {
+       set matches [longid $id]
+       if {[llength $matches] > 0} {
+           if {[llength $matches] > 1} return
+           set known 1
+           set id [lindex $matches 0]
+       }
+    } else {
+       set known [commitinview $id $curview]
+    }
+    if {$known} {
        $ctext tag conf $lk -foreground blue -underline 1
-       $ctext tag bind $lk <1> [list selectline [rowofcommit $id] 1]
+       $ctext tag bind $lk <1> [list selbyid $id]
        $ctext tag bind $lk <Enter> {linkcursor %W 1}
        $ctext tag bind $lk <Leave> {linkcursor %W -1}
     } else {
        lappend pendinglinks($id) $lk
-       lappend commitinterest($id) {makelink %I}
+       interestedin $id {makelink %P}
     }
 }
 
@@ -6279,6 +6494,7 @@ proc gettreeline {gtf id} {
 
 proc showfile {f} {
     global treefilelist treeidlist diffids nullid nullid2
+    global ctext_file_names ctext_file_lines
     global ctext commentend
 
     set i [lsearch -exact $treefilelist($diffids) $f]
@@ -6302,6 +6518,8 @@ proc showfile {f} {
     filerun $bf [list getblobline $bf $diffids]
     $ctext config -state normal
     clear_ctext $commentend
+    lappend ctext_file_names $f
+    lappend ctext_file_lines [lindex [split $commentend "."] 0]
     $ctext insert end "\n"
     $ctext insert end "$f\n" filesep
     $ctext config -state disabled
@@ -6333,7 +6551,7 @@ proc getblobline {bf id} {
 
 proc mergediff {id} {
     global diffmergeid mdifffd
-    global diffids
+    global diffids treediffs
     global parents
     global diffcontext
     global diffencoding
@@ -6341,6 +6559,7 @@ proc mergediff {id} {
 
     set diffmergeid $id
     set diffids $id
+    set treediffs($id) {}
     # this doesn't seem to actually affect anything...
     set cmd [concat | git diff-tree --no-commit-id --cc -U$diffcontext $id]
     if {$limitdiffs && $vfilelimit($curview) ne {}} {
@@ -6360,7 +6579,8 @@ proc mergediff {id} {
 
 proc getmergediffline {mdf id np} {
     global diffmergeid ctext cflist mergemax
-    global difffilestart mdifffd
+    global difffilestart mdifffd treediffs
+    global ctext_file_names ctext_file_lines
     global diffencoding
 
     $ctext conf -state normal
@@ -6377,7 +6597,10 @@ proc getmergediffline {mdf id np} {
            $ctext insert end "\n"
            set here [$ctext index "end - 1c"]
            lappend difffilestart $here
+           lappend treediffs($id) $fname
            add_flist [list $fname]
+           lappend ctext_file_names $fname
+           lappend ctext_file_lines [lindex [split $here "."] 0]
            set diffencoding [get_path_encoding $fname]
            set l [expr {(78 - [string length $fname]) / 2}]
            set pad [string range "----------------------------------------" 1 $l]
@@ -6646,11 +6869,13 @@ proc setinlist {var i val} {
 
 proc makediffhdr {fname ids} {
     global ctext curdiffstart treediffs
+    global ctext_file_names
 
     set i [lsearch -exact $treediffs($ids) $fname]
     if {$i >= 0} {
        setinlist difffilestart $i $curdiffstart
     }
+    set ctext_file_names [lreplace $ctext_file_names end end $fname]
     set l [expr {(78 - [string length $fname]) / 2}]
     set pad [string range "----------------------------------------" 1 $l]
     $ctext insert $curdiffstart "$pad $fname $pad" filesep
@@ -6659,6 +6884,7 @@ proc makediffhdr {fname ids} {
 proc getblobdiffline {bdf ids} {
     global diffids blobdifffd ctext curdiffstart
     global diffnexthead diffnextnote difffilestart
+    global ctext_file_names ctext_file_lines
     global diffinhdr treediffs
     global diffencoding
 
@@ -6676,6 +6902,8 @@ proc getblobdiffline {bdf ids} {
            # start of a new file
            $ctext insert end "\n"
            set curdiffstart [$ctext index "end - 1c"]
+           lappend ctext_file_names ""
+           lappend ctext_file_lines [lindex [split $curdiffstart "."] 0]
            $ctext insert end "\n" filesep
            # If the name hasn't changed the length will be odd,
            # the middle char will be a space, and the two bits either
@@ -6812,6 +7040,7 @@ proc nextfile {} {
 
 proc clear_ctext {{first 1.0}} {
     global ctext smarktop smarkbot
+    global ctext_file_names ctext_file_lines
     global pendinglinks
 
     set l [lindex [split $first .] 0]
@@ -6825,6 +7054,8 @@ proc clear_ctext {{first 1.0}} {
     if {$first eq "1.0"} {
        catch {unset pendinglinks}
     }
+    set ctext_file_names {}
+    set ctext_file_lines {}
 }
 
 proc settabs {{firstab {}}} {
@@ -7100,13 +7331,13 @@ proc gotocommit {} {
     } else {
        set id [string tolower $sha1string]
        if {[regexp {^[0-9a-f]{4,39}$} $id]} {
-           set matches [array names varcid "$curview,$id*"]
+           set matches [longid $id]
            if {$matches ne {}} {
                if {[llength $matches] > 1} {
                    error_popup [mc "Short SHA1 id %s is ambiguous" $id]
                    return
                }
-               set id [lindex [split [lindex $matches 0] ","] 1]
+               set id [lindex $matches 0]
            }
        }
     }
@@ -7323,9 +7554,9 @@ proc rowmenu {x y id} {
     } else {
        set menu $fakerowmenu
     }
-    $menu entryconfigure [mc "Diff this -> selected"] -state $state
-    $menu entryconfigure [mc "Diff selected -> this"] -state $state
-    $menu entryconfigure [mc "Make patch"] -state $state
+    $menu entryconfigure [mca "Diff this -> selected"] -state $state
+    $menu entryconfigure [mca "Diff selected -> this"] -state $state
+    $menu entryconfigure [mca "Make patch"] -state $state
     tk_popup $menu $x $y
 }
 
@@ -7625,6 +7856,7 @@ proc mkbranch {} {
     grid $top.id $top.sha1 -sticky w
     label $top.nlab -text [mc "Name:"]
     entry $top.name -width 40
+    bind $top.name <Key-Return> "[list mkbrgo $top]"
     grid $top.nlab $top.name -sticky w
     frame $top.buts
     button $top.buts.go -text [mc "Create"] -command [list mkbrgo $top]
@@ -7641,24 +7873,42 @@ proc mkbrgo {top} {
 
     set name [$top.name get]
     set id [$top.sha1 get]
+    set cmdargs {}
+    set old_id {}
     if {$name eq {}} {
        error_popup [mc "Please specify a name for the new branch"]
        return
     }
+    if {[info exists headids($name)]} {
+       if {![confirm_popup [mc \
+               "Branch '%s' already exists. Overwrite?" $name]]} {
+           return
+       }
+       set old_id $headids($name)
+       lappend cmdargs -f
+    }
     catch {destroy $top}
+    lappend cmdargs $name $id
     nowbusy newbranch
     update
     if {[catch {
-       exec git branch $name $id
+       eval exec git branch $cmdargs
     } err]} {
        notbusy newbranch
        error_popup $err
     } else {
-       set headids($name) $id
-       lappend idheads($id) $name
-       addedhead $id $name
        notbusy newbranch
-       redrawtags $id
+       if {$old_id ne {}} {
+           movehead $id $name
+           movedhead $id $name
+           redrawtags $old_id
+           redrawtags $id
+       } else {
+           set headids($name) $id
+           lappend idheads($id) $name
+           addedhead $id $name
+           redrawtags $id
+       }
        dispneartags 0
        run refill_reflist
     }
@@ -7953,7 +8203,7 @@ proc reflistfilter_change {n1 n2 op} {
 
 proc refill_reflist {} {
     global reflist reflistfilter showrefstop headids tagids otherrefids
-    global curview commitinterest
+    global curview
 
     if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
     set refs {}
@@ -7962,7 +8212,7 @@ proc refill_reflist {} {
            if {[commitinview $headids($n) $curview]} {
                lappend refs [list $n H]
            } else {
-               set commitinterest($headids($n)) {run refill_reflist}
+               interestedin $headids($n) {run refill_reflist}
            }
        }
     }
@@ -7971,7 +8221,7 @@ proc refill_reflist {} {
            if {[commitinview $tagids($n) $curview]} {
                lappend refs [list $n T]
            } else {
-               set commitinterest($tagids($n)) {run refill_reflist}
+               interestedin $tagids($n) {run refill_reflist}
            }
        }
     }
@@ -7980,7 +8230,7 @@ proc refill_reflist {} {
            if {[commitinview $otherrefids($n) $curview]} {
                lappend refs [list $n o]
            } else {
-               set commitinterest($otherrefids($n)) {run refill_reflist}
+               interestedin $otherrefids($n) {run refill_reflist}
            }
        }
     }
@@ -10145,8 +10395,8 @@ if {$cmdline_files ne {} || $revtreeargs ne {} || $revtreeargscmd ne {}} {
     set viewperm(1) 0
     set vdatemode(1) 0
     addviewmenu 1
-    .bar.view entryconf [mc "Edit view..."] -state normal
-    .bar.view entryconf [mc "Delete view"] -state normal
+    .bar.view entryconf [mca "Edit view..."] -state normal
+    .bar.view entryconf [mca "Delete view"] -state normal
 }
 
 if {[info exists permviews]} {