Merge branch 'dev'
authorPaul Mackerras <paulus@samba.org>
Sun, 9 Nov 2008 11:05:50 +0000 (22:05 +1100)
committerPaul Mackerras <paulus@samba.org>
Sun, 9 Nov 2008 11:05:50 +0000 (22:05 +1100)
1  2 
gitk
diff --combined gitk
index 2048798bcf7c1207f7ea86ed5cfad2d34fd22f25,bea5026f62083e66427dfb369f5b41b0522ed141..18d153e569bdadb92ce67d161596779a8b25dc6a
--- 1/gitk
--- 2/gitk
+++ b/gitk
@@@ -1746,24 -1746,22 +1746,24 @@@ proc show_error {w top msg} 
      pack $w.ok -side bottom -fill x
      bind $top <Visibility> "grab $top; focus $top"
      bind $top <Key-Return> "destroy $top"
 +    bind $top <Key-space>  "destroy $top"
 +    bind $top <Key-Escape> "destroy $top"
      tkwait window $top
  }
  
 -proc error_popup msg {
 +proc error_popup {msg {owner .}} {
      set w .error
      toplevel $w
 -    wm transient $w .
 +    wm transient $w $owner
      show_error $w $w $msg
  }
  
 -proc confirm_popup msg {
 +proc confirm_popup {msg {owner .}} {
      global confirm_ok
      set confirm_ok 0
      set w .confirm
      toplevel $w
 -    wm transient $w .
 +    wm transient $w $owner
      message $w.m -text $msg -justify center -aspect 400
      pack $w.m -side top -fill x -padx 20 -pady 20
      button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
      button $w.cancel -text [mc Cancel] -command "destroy $w"
      pack $w.cancel -side right -fill x
      bind $w <Visibility> "grab $w; focus $w"
 +    bind $w <Key-Return> "set confirm_ok 1; destroy $w"
 +    bind $w <Key-space>  "set confirm_ok 1; destroy $w"
 +    bind $w <Key-Escape> "destroy $w"
      tkwait window $w
      return $confirm_ok
  }
@@@ -2103,7 -2098,7 +2103,7 @@@ proc makewindow {} 
      $ctext tag conf filesep -font textfontbold -back "#aaaaaa"
      $ctext tag conf hunksep -fore [lindex $diffcolors 2]
      $ctext tag conf d0 -fore [lindex $diffcolors 0]
-     $ctext tag conf d1 -fore [lindex $diffcolors 1]
+     $ctext tag conf dresult -fore [lindex $diffcolors 1]
      $ctext tag conf m0 -fore red
      $ctext tag conf m1 -fore blue
      $ctext tag conf m2 -fore green
@@@ -2546,7 -2541,6 +2546,7 @@@ proc about {} 
      }
      toplevel $w
      wm title $w [mc "About gitk"]
 +    wm transient $w .
      message $w.m -text [mc "
  Gitk - a commit viewer for git
  
@@@ -2575,7 -2569,6 +2575,7 @@@ proc keys {} 
      }
      toplevel $w
      wm title $w [mc "Gitk key bindings"]
 +    wm transient $w .
      message $w.m -text "
  [mc "Gitk key bindings:"]
  
            -justify left -bg white -border 2 -relief groove
      pack $w.m -side top -fill both -padx 2 -pady 2
      button $w.ok -text [mc "Close"] -command "destroy $w" -default active
 +    bind $w <Key-Escape> [list destroy $w]
      pack $w.ok -side bottom
      bind $w <Visibility> "focus $w.ok"
      bind $w <Key-Escape> "destroy $w"
@@@ -3225,7 -3217,7 +3225,7 @@@ proc find_hunk_blamespec {base line} 
  }
  
  proc external_blame_diff {} {
-     global currentid diffmergeid cmitmode
+     global currentid cmitmode
      global diff_menu_txtpos diff_menu_line
      global diff_menu_filebase flist_menu_file
  
      external_blame $parent_idx $line
  }
  
+ # Find the SHA1 ID of the blob for file $fname in the index
+ # at stage 0 or 2
+ proc index_sha1 {fname} {
+     set f [open [list | git ls-files -s $fname] r]
+     while {[gets $f line] >= 0} {
+       set info [lindex [split $line "\t"] 0]
+       set stage [lindex $info 2]
+       if {$stage eq "0" || $stage eq "2"} {
+           close $f
+           return [lindex $info 1]
+       }
+     }
+     close $f
+     return {}
+ }
  proc external_blame {parent_idx {line {}}} {
      global flist_menu_file
      global nullid nullid2
  proc show_line_source {} {
      global cmitmode currentid parents curview blamestuff blameinst
      global diff_menu_line diff_menu_filebase flist_menu_file
+     global nullid nullid2 gitdir
  
+     set from_index {}
      if {$cmitmode eq "tree"} {
        set id $currentid
        set line [expr {$diff_menu_line - $diff_menu_filebase}]
            mark_ctext_line $diff_menu_line
            return
        }
-       set id [lindex $parents($curview,$currentid) [expr {$pi - 1}]]
+       incr pi -1
+       if {$currentid eq $nullid} {
+           if {$pi > 0} {
+               # must be a merge in progress...
+               if {[catch {
+                   # get the last line from .git/MERGE_HEAD
+                   set f [open [file join $gitdir MERGE_HEAD] r]
+                   set id [lindex [split [read $f] "\n"] end-1]
+                   close $f
+               } err]} {
+                   error_popup [mc "Couldn't read merge head: %s" $err]
+                   return
+               }
+           } elseif {$parents($curview,$currentid) eq $nullid2} {
+               # need to do the blame from the index
+               if {[catch {
+                   set from_index [index_sha1 $flist_menu_file]
+               } err]} {
+                   error_popup [mc "Error reading index: %s" $err]
+                   return
+               }
+           }
+       } else {
+           set id [lindex $parents($curview,$currentid) $pi]
+       }
        set line [lindex $h 1]
      }
+     set blameargs {}
+     if {$from_index ne {}} {
+       lappend blameargs | git cat-file blob $from_index
+     }
+     lappend blameargs | git blame -p -L$line,+1
+     if {$from_index ne {}} {
+       lappend blameargs --contents -
+     } else {
+       lappend blameargs $id
+     }
+     lappend blameargs -- $flist_menu_file
      if {[catch {
-       set f [open [list | git blame -p -L$line,+1 $id -- $flist_menu_file] r]
+       set f [open $blameargs r]
      } err]} {
        error_popup [mc "Couldn't start git blame: %s" $err]
        return
@@@ -3313,7 -3358,7 +3366,7 @@@ proc stopblaming {} 
  }
  
  proc read_line_source {fd inst} {
-     global blamestuff curview commfd blameinst
+     global blamestuff curview commfd blameinst nullid nullid2
  
      while {[gets $fd line] >= 0} {
        lappend blamestuff($inst) $line
      }
      if {$fname ne {}} {
        # all looks good, select it
+       if {$id eq $nullid} {
+           # blame uses all-zeroes to mean not committed,
+           # which would mean a change in the index
+           set id $nullid2
+       }
        if {[commitinview $id $curview]} {
            selectline [rowofcommit $id] 1 [list $fname $lnum]
        } else {
@@@ -3467,8 -3517,8 +3525,8 @@@ proc shellsplit {str} 
  # Code to implement multiple views
  
  proc newview {ishighlight} {
 -    global nextviewnum newviewname newviewperm newishighlight
 -    global newviewargs revtreeargs viewargscmd newviewargscmd curview
 +    global nextviewnum newviewname newishighlight
 +    global revtreeargs viewargscmd newviewopts curview
  
      set newishighlight $ishighlight
      set top .gitkview
        return
      }
      set newviewname($nextviewnum) "[mc "View"] $nextviewnum"
 -    set newviewperm($nextviewnum) 0
 -    set newviewargs($nextviewnum) [shellarglist $revtreeargs]
 -    set newviewargscmd($nextviewnum) $viewargscmd($curview)
 +    set newviewopts($nextviewnum,perm) 0
 +    set newviewopts($nextviewnum,cmd)  $viewargscmd($curview)
 +    decode_view_opts $nextviewnum $revtreeargs
      vieweditor $top $nextviewnum [mc "Gitk view definition"]
  }
  
 +set known_view_options {
 +    {perm    b    . {}               {mc "Remember this view"}}
 +    {args    t50= + {}               {mc "Commits to include (arguments to git log):"}}
 +    {all     b    * "--all"          {mc "Use all refs"}}
 +    {dorder  b    . {"--date-order" "-d"}      {mc "Strictly sort by date"}}
 +    {lright  b    . "--left-right"   {mc "Mark branch sides"}}
 +    {since   t15  + {"--since=*" "--after=*"}  {mc "Since date:"}}
 +    {until   t15  . {"--until=*" "--before=*"} {mc "Until date:"}}
 +    {limit   t10  + "--max-count=*"  {mc "Max count:"}}
 +    {skip    t10  . "--skip=*"       {mc "Skip:"}}
 +    {first   b    . "--first-parent" {mc "Limit to first parent"}}
 +    {cmd     t50= + {}               {mc "Command to generate more commits to include:"}}
 +    }
 +
 +proc encode_view_opts {n} {
 +    global known_view_options newviewopts
 +
 +    set rargs [list]
 +    foreach opt $known_view_options {
 +      set patterns [lindex $opt 3]
 +      if {$patterns eq {}} continue
 +      set pattern [lindex $patterns 0]
 +
 +      set val $newviewopts($n,[lindex $opt 0])
 +      
 +      if {[lindex $opt 1] eq "b"} {
 +          if {$val} {
 +              lappend rargs $pattern
 +          }
 +      } else {
 +          set val [string trim $val]
 +          if {$val ne {}} {
 +              set pfix [string range $pattern 0 end-1]
 +              lappend rargs $pfix$val
 +          }
 +      }
 +    }
 +    return [concat $rargs [shellsplit $newviewopts($n,args)]]
 +}
 +
 +proc decode_view_opts {n view_args} {
 +    global known_view_options newviewopts
 +
 +    foreach opt $known_view_options {
 +      if {[lindex $opt 1] eq "b"} {
 +          set val 0
 +      } else {
 +          set val {}
 +      }
 +      set newviewopts($n,[lindex $opt 0]) $val
 +    }
 +    set oargs [list]
 +    foreach arg $view_args {
 +      if {[regexp -- {^-([0-9]+)$} $arg arg cnt]
 +          && ![info exists found(limit)]} {
 +          set newviewopts($n,limit) $cnt
 +          set found(limit) 1
 +          continue
 +      }
 +      catch { unset val }
 +      foreach opt $known_view_options {
 +          set id [lindex $opt 0]
 +          if {[info exists found($id)]} continue
 +          foreach pattern [lindex $opt 3] {
 +              if {![string match $pattern $arg]} continue
 +              if {[lindex $opt 1] ne "b"} {
 +                  set size [string length $pattern]
 +                  set val [string range $arg [expr {$size-1}] end]
 +              } else {
 +                  set val 1
 +              }
 +              set newviewopts($n,$id) $val
 +              set found($id) 1
 +              break
 +          }
 +          if {[info exists val]} break
 +      }
 +      if {[info exists val]} continue
 +      lappend oargs $arg
 +    }
 +    set newviewopts($n,args) [shellarglist $oargs]
 +}
 +
  proc editview {} {
      global curview
 -    global viewname viewperm newviewname newviewperm
 -    global viewargs newviewargs viewargscmd newviewargscmd
 +    global viewname viewperm newviewname newviewopts
 +    global viewargs viewargscmd
  
      set top .gitkvedit-$curview
      if {[winfo exists $top]} {
        raise $top
        return
      }
 -    set newviewname($curview) $viewname($curview)
 -    set newviewperm($curview) $viewperm($curview)
 -    set newviewargs($curview) [shellarglist $viewargs($curview)]
 -    set newviewargscmd($curview) $viewargscmd($curview)
 +    set newviewname($curview)      $viewname($curview)
 +    set newviewopts($curview,perm) $viewperm($curview)
 +    set newviewopts($curview,cmd)  $viewargscmd($curview)
 +    decode_view_opts $curview $viewargs($curview)
      vieweditor $top $curview "Gitk: edit view $viewname($curview)"
  }
  
  proc vieweditor {top n title} {
 -    global newviewname newviewperm viewfiles bgcolor
 +    global newviewname newviewopts viewfiles bgcolor
 +    global known_view_options
  
      toplevel $top
      wm title $top $title
 +    wm transient $top .
 +
 +    # View name
 +    frame $top.nfr
      label $top.nl -text [mc "Name"]
      entry $top.name -width 20 -textvariable newviewname($n)
 -    grid $top.nl $top.name -sticky w -pady 5
 -    checkbutton $top.perm -text [mc "Remember this view"] \
 -      -variable newviewperm($n)
 -    grid $top.perm - -pady 5 -sticky w
 -    message $top.al -aspect 1000 \
 -      -text [mc "Commits to include (arguments to git log):"]
 -    grid $top.al - -sticky w -pady 5
 -    entry $top.args -width 50 -textvariable newviewargs($n) \
 -      -background $bgcolor
 -    grid $top.args - -sticky ew -padx 5
 -
 -    message $top.ac -aspect 1000 \
 -      -text [mc "Command to generate more commits to include:"]
 -    grid $top.ac - -sticky w -pady 5
 -    entry $top.argscmd -width 50 -textvariable newviewargscmd($n) \
 -      -background white
 -    grid $top.argscmd - -sticky ew -padx 5
 -
 -    message $top.l -aspect 1000 \
 +    pack $top.nfr -in $top -fill x -pady 5 -padx 3
 +    pack $top.nl -in $top.nfr -side left -padx {0 30}
 +    pack $top.name -in $top.nfr -side left
 +
 +    # View options
 +    set cframe $top.nfr
 +    set cexpand 0
 +    set cnt 0
 +    foreach opt $known_view_options {
 +      set id [lindex $opt 0]
 +      set type [lindex $opt 1]
 +      set flags [lindex $opt 2]
 +      set title [eval [lindex $opt 4]]
 +      set lxpad 0
 +
 +      if {$flags eq "+" || $flags eq "*"} {
 +          set cframe $top.fr$cnt
 +          incr cnt
 +          frame $cframe
 +          pack $cframe -in $top -fill x -pady 3 -padx 3
 +          set cexpand [expr {$flags eq "*"}]
 +      } else {
 +          set lxpad 5
 +      }
 +
 +      if {$type eq "b"} {
 +          checkbutton $cframe.c_$id -text $title -variable newviewopts($n,$id)
 +          pack $cframe.c_$id -in $cframe -side left \
 +              -padx [list $lxpad 0] -expand $cexpand -anchor w
 +      } elseif {[regexp {^t(\d+)$} $type type sz]} {
 +          message $cframe.l_$id -aspect 1500 -text $title
 +          entry $cframe.e_$id -width $sz -background $bgcolor \
 +              -textvariable newviewopts($n,$id)
 +          pack $cframe.l_$id -in $cframe -side left -padx [list $lxpad 0]
 +          pack $cframe.e_$id -in $cframe -side left -expand 1 -fill x
 +      } elseif {[regexp {^t(\d+)=$} $type type sz]} {
 +          message $cframe.l_$id -aspect 1500 -text $title
 +          entry $cframe.e_$id -width $sz -background $bgcolor \
 +              -textvariable newviewopts($n,$id)
 +          pack $cframe.l_$id -in $cframe -side top -pady [list 3 0] -anchor w
 +          pack $cframe.e_$id -in $cframe -side top -fill x
 +      }
 +    }
 +
 +    # Path list
 +    message $top.l -aspect 1500 \
        -text [mc "Enter files and directories to include, one per line:"]
 -    grid $top.l - -sticky w
 -    text $top.t -width 40 -height 10 -background $bgcolor -font uifont
 +    pack $top.l -in $top -side top -pady [list 7 0] -anchor w -padx 3
 +    text $top.t -width 40 -height 5 -background $bgcolor -font uifont
      if {[info exists viewfiles($n)]} {
        foreach f $viewfiles($n) {
            $top.t insert end $f
        $top.t delete {end - 1c} end
        $top.t mark set insert 0.0
      }
 -    grid $top.t - -sticky ew -padx 5
 +    pack $top.t -in $top -side top -pady [list 0 5] -fill both -expand 1 -padx 3
      frame $top.buts
      button $top.buts.ok -text [mc "OK"] -command [list newviewok $top $n]
 +    button $top.buts.apply -text [mc "Apply (F5)"] -command [list newviewok $top $n 1]
      button $top.buts.can -text [mc "Cancel"] -command [list destroy $top]
 -    grid $top.buts.ok $top.buts.can
 +    bind $top <Control-Return> [list newviewok $top $n]
 +    bind $top <F5> [list newviewok $top $n 1]
 +    bind $top <Escape> [list destroy $top]
 +    grid $top.buts.ok $top.buts.apply $top.buts.can
      grid columnconfigure $top.buts 0 -weight 1 -uniform a
      grid columnconfigure $top.buts 1 -weight 1 -uniform a
 -    grid $top.buts - -pady 10 -sticky ew
 +    grid columnconfigure $top.buts 2 -weight 1 -uniform a
 +    pack $top.buts -in $top -side top -fill x
      focus $top.t
  }
  
@@@ -3685,15 -3615,17 +3743,15 @@@ proc allviewmenus {n op args} 
      # doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
  }
  
 -proc newviewok {top n} {
 +proc newviewok {top n {apply 0}} {
      global nextviewnum newviewperm newviewname newishighlight
      global viewname viewfiles viewperm selectedview curview
 -    global viewargs newviewargs viewargscmd newviewargscmd viewhlmenu
 +    global viewargs viewargscmd newviewopts viewhlmenu
  
      if {[catch {
 -      set newargs [shellsplit $newviewargs($n)]
 +      set newargs [encode_view_opts $n]
      } err]} {
 -      error_popup "[mc "Error in commit selection arguments:"] $err"
 -      wm raise $top
 -      focus $top
 +      error_popup "[mc "Error in commit selection arguments:"] $err" $top
        return
      }
      set files {}
        # creating a new view
        incr nextviewnum
        set viewname($n) $newviewname($n)
 -      set viewperm($n) $newviewperm($n)
 +      set viewperm($n) $newviewopts($n,perm)
        set viewfiles($n) $files
        set viewargs($n) $newargs
 -      set viewargscmd($n) $newviewargscmd($n)
 +      set viewargscmd($n) $newviewopts($n,cmd)
        addviewmenu $n
        if {!$newishighlight} {
            run showview $n
        }
      } else {
        # editing an existing view
 -      set viewperm($n) $newviewperm($n)
 +      set viewperm($n) $newviewopts($n,perm)
        if {$newviewname($n) ne $viewname($n)} {
            set viewname($n) $newviewname($n)
            doviewmenu .bar.view 5 [list showview $n] \
                # entryconf [list -label $viewname($n) -value $viewname($n)]
        }
        if {$files ne $viewfiles($n) || $newargs ne $viewargs($n) || \
 -              $newviewargscmd($n) ne $viewargscmd($n)} {
 +              $newviewopts($n,cmd) ne $viewargscmd($n)} {
            set viewfiles($n) $files
            set viewargs($n) $newargs
 -          set viewargscmd($n) $newviewargscmd($n)
 +          set viewargscmd($n) $newviewopts($n,cmd)
            if {$curview == $n} {
                run reloadcommits
            }
        }
      }
 +    if {$apply} return
      catch {destroy $top}
  }
  
@@@ -6795,130 -6726,16 +6853,16 @@@ proc mark_ctext_line {lnum} 
  }
  
  proc mergediff {id} {
-     global diffmergeid mdifffd
+     global diffmergeid
      global diffids treediffs
-     global parents
-     global diffcontext
-     global diffencoding
-     global limitdiffs vfilelimit curview
-     global targetline
+     global parents curview
  
      set diffmergeid $id
      set diffids $id
      set treediffs($id) {}
-     set targetline {}
-     # 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 {}} {
-       set cmd [concat $cmd -- $vfilelimit($curview)]
-     }
-     if {[catch {set mdf [open $cmd r]} err]} {
-       error_popup "[mc "Error getting merge diffs:"] $err"
-       return
-     }
-     fconfigure $mdf -blocking 0 -encoding binary
-     set mdifffd($id) $mdf
      set np [llength $parents($curview,$id)]
-     set diffencoding [get_path_encoding {}]
      settabs $np
-     filerun $mdf [list getmergediffline $mdf $id $np]
- }
- proc getmergediffline {mdf id np} {
-     global diffmergeid ctext cflist mergemax
-     global difffilestart mdifffd treediffs
-     global ctext_file_names ctext_file_lines
-     global diffencoding jump_to_here targetline diffline
-     $ctext conf -state normal
-     set nr 0
-     while {[incr nr] <= 1000 && [gets $mdf line] >= 0} {
-       if {![info exists diffmergeid] || $id != $diffmergeid
-           || $mdf != $mdifffd($id)} {
-           close $mdf
-           return 0
-       }
-       if {[regexp {^diff --cc (.*)} $line match fname]} {
-           # start of a new file
-           set fname [encoding convertfrom $fname]
-           $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]
-           $ctext insert end "$pad $fname $pad\n" filesep
-           set targetline {}
-           if {$jump_to_here ne {} && [lindex $jump_to_here 0] eq $fname} {
-               set targetline [lindex $jump_to_here 1]
-           }
-           set diffline 0
-       } elseif {[regexp {^@@} $line]} {
-           set line [encoding convertfrom $diffencoding $line]
-           $ctext insert end "$line\n" hunksep
-           if {[regexp { \+(\d+),\d+ @@} $line m nl]} {
-               set diffline $nl
-           }
-       } elseif {[regexp {^[0-9a-f]{40}$} $line] || [regexp {^index} $line]} {
-           # do nothing
-       } else {
-           set line [encoding convertfrom $diffencoding $line]
-           # parse the prefix - one ' ', '-' or '+' for each parent
-           set spaces {}
-           set minuses {}
-           set pluses {}
-           set isbad 0
-           for {set j 0} {$j < $np} {incr j} {
-               set c [string range $line $j $j]
-               if {$c == " "} {
-                   lappend spaces $j
-               } elseif {$c == "-"} {
-                   lappend minuses $j
-               } elseif {$c == "+"} {
-                   lappend pluses $j
-               } else {
-                   set isbad 1
-                   break
-               }
-           }
-           set tags {}
-           set num {}
-           if {!$isbad && $minuses ne {} && $pluses eq {}} {
-               # line doesn't appear in result, parents in $minuses have the line
-               set num [lindex $minuses 0]
-           } elseif {!$isbad && $pluses ne {} && $minuses eq {}} {
-               # line appears in result, parents in $pluses don't have the line
-               lappend tags mresult
-               set num [lindex $spaces 0]
-           }
-           if {$num ne {}} {
-               if {$num >= $mergemax} {
-                   set num "max"
-               }
-               lappend tags m$num
-           }
-           $ctext insert end "$line\n" $tags
-           if {$targetline ne {} && $minuses eq {}} {
-               if {$diffline == $targetline} {
-                   set here [$ctext index "end - 1 line"]
-                   mark_ctext_line [lindex [split $here .] 0]
-                   set targetline {}
-               } else {
-                   incr diffline
-               }
-           }
-       }
-     }
-     $ctext conf -state disabled
-     if {[eof $mdf]} {
-       close $mdf
-       return 0
-     }
-     return [expr {$nr >= 1000? 2: 1}]
+     getblobdiffs $id
  }
  
  proc startdiff {ids} {
@@@ -7037,8 -6854,10 +6981,10 @@@ proc gettreediffline {gdtf ids} 
                set file [lindex $file 0]
            }
            set file [encoding convertfrom $file]
-           lappend treediff $file
-           lappend sublist $file
+           if {$file ne [lindex $treediff end]} {
+               lappend treediff $file
+               lappend sublist $file
+           }
        }
      }
      if {$perfile_attrs} {
@@@ -7098,9 -6917,9 +7044,9 @@@ proc getblobdiffs {ids} 
      global diffcontext
      global ignorespace
      global limitdiffs vfilelimit curview
-     global diffencoding targetline
+     global diffencoding targetline diffnparents
  
-     set cmd [diffcmd $ids "-p -C --no-commit-id -U$diffcontext"]
+     set cmd [diffcmd $ids "-p -C --cc --no-commit-id -U$diffcontext"]
      if {$ignorespace} {
        append cmd " -w"
      }
        set cmd [concat $cmd -- $vfilelimit($curview)]
      }
      if {[catch {set bdf [open $cmd r]} err]} {
-       puts "error getting diffs: $err"
+       error_popup [mc "Error getting diffs: %s" $err]
        return
      }
      set targetline {}
+     set diffnparents 0
      set diffinhdr 0
      set diffencoding [get_path_encoding {}]
      fconfigure $bdf -blocking 0 -encoding binary
@@@ -7133,14 -6953,16 +7080,16 @@@ proc setinlist {var i val} 
  }
  
  proc makediffhdr {fname ids} {
-     global ctext curdiffstart treediffs
+     global ctext curdiffstart treediffs diffencoding
      global ctext_file_names jump_to_here targetline diffline
  
+     set fname [encoding convertfrom $fname]
+     set diffencoding [get_path_encoding $fname]
      set i [lsearch -exact $treediffs($ids) $fname]
      if {$i >= 0} {
        setinlist difffilestart $i $curdiffstart
      }
-     set ctext_file_names [lreplace $ctext_file_names end end $fname]
+     lset ctext_file_names end $fname
      set l [expr {(78 - [string length $fname]) / 2}]
      set pad [string range "----------------------------------------" 1 $l]
      $ctext insert $curdiffstart "$pad $fname $pad" filesep
@@@ -7155,7 -6977,7 +7104,7 @@@ proc getblobdiffline {bdf ids} 
      global diffids blobdifffd ctext curdiffstart
      global diffnexthead diffnextnote difffilestart
      global ctext_file_names ctext_file_lines
-     global diffinhdr treediffs
+     global diffinhdr treediffs mergemax diffnparents
      global diffencoding jump_to_here targetline diffline
  
      set nr 0
            close $bdf
            return 0
        }
-       if {![string compare -length 11 "diff --git " $line]} {
-           # trim off "diff --git "
-           set line [string range $line 11 end]
-           set diffinhdr 1
+       if {![string compare -length 5 "diff " $line]} {
+           if {![regexp {^diff (--cc|--git) } $line m type]} {
+               set line [encoding convertfrom $line]
+               $ctext insert end "$line\n" hunksep
+               continue
+           }
            # start of a new file
+           set diffinhdr 1
            $ctext insert end "\n"
            set curdiffstart [$ctext index "end - 1c"]
            lappend ctext_file_names ""
            lappend ctext_file_lines [lindex [split $curdiffstart "."] 0]
            $ctext insert end "\n" filesep
-           # If the name hasn't changed the length will be odd,
-           # the middle char will be a space, and the two bits either
-           # side will be a/name and b/name, or "a/name" and "b/name".
-           # If the name has changed we'll get "rename from" and
-           # "rename to" or "copy from" and "copy to" lines following this,
-           # and we'll use them to get the filenames.
-           # This complexity is necessary because spaces in the filename(s)
-           # don't get escaped.
-           set l [string length $line]
-           set i [expr {$l / 2}]
-           if {!(($l & 1) && [string index $line $i] eq " " &&
-                 [string range $line 2 [expr {$i - 1}]] eq \
-                     [string range $line [expr {$i + 3}] end])} {
-               continue
-           }
-           # unescape if quoted and chop off the a/ from the front
-           if {[string index $line 0] eq "\""} {
-               set fname [string range [lindex $line 0] 2 end]
+           if {$type eq "--cc"} {
+               # start of a new file in a merge diff
+               set fname [string range $line 10 end]
+               if {[lsearch -exact $treediffs($ids) $fname] < 0} {
+                   lappend treediffs($ids) $fname
+                   add_flist [list $fname]
+               }
            } else {
-               set fname [string range $line 2 [expr {$i - 1}]]
+               set line [string range $line 11 end]
+               # If the name hasn't changed the length will be odd,
+               # the middle char will be a space, and the two bits either
+               # side will be a/name and b/name, or "a/name" and "b/name".
+               # If the name has changed we'll get "rename from" and
+               # "rename to" or "copy from" and "copy to" lines following
+               # this, and we'll use them to get the filenames.
+               # This complexity is necessary because spaces in the
+               # filename(s) don't get escaped.
+               set l [string length $line]
+               set i [expr {$l / 2}]
+               if {!(($l & 1) && [string index $line $i] eq " " &&
+                     [string range $line 2 [expr {$i - 1}]] eq \
+                         [string range $line [expr {$i + 3}] end])} {
+                   continue
+               }
+               # unescape if quoted and chop off the a/ from the front
+               if {[string index $line 0] eq "\""} {
+                   set fname [string range [lindex $line 0] 2 end]
+               } else {
+                   set fname [string range $line 2 [expr {$i - 1}]]
+               }
            }
-           set fname [encoding convertfrom $fname]
-           set diffencoding [get_path_encoding $fname]
            makediffhdr $fname $ids
  
-       } elseif {[regexp {^@@ -([0-9]+)(,[0-9]+)? \+([0-9]+)(,[0-9]+)? @@(.*)} \
-                      $line match f1l f1c f2l f2c rest]} {
+       } elseif {![string compare -length 16 "* Unmerged path " $line]} {
+           set fname [encoding convertfrom [string range $line 16 end]]
+           $ctext insert end "\n"
+           set curdiffstart [$ctext index "end - 1c"]
+           lappend ctext_file_names $fname
+           lappend ctext_file_lines [lindex [split $curdiffstart "."] 0]
+           $ctext insert end "$line\n" filesep
+           set i [lsearch -exact $treediffs($ids) $fname]
+           if {$i >= 0} {
+               setinlist difffilestart $i $curdiffstart
+           }
+       } elseif {![string compare -length 2 "@@" $line]} {
+           regexp {^@@+} $line ats
            set line [encoding convertfrom $diffencoding $line]
            $ctext insert end "$line\n" hunksep
+           if {[regexp { \+(\d+),\d+ @@} $line m nl]} {
+               set diffline $nl
+           }
+           set diffnparents [expr {[string length $ats] - 1}]
            set diffinhdr 0
-           set diffline $f2l
  
        } elseif {$diffinhdr} {
            if {![string compare -length 12 "rename from " $line]} {
                if {[string index $fname 0] eq "\""} {
                    set fname [lindex $fname 0]
                }
-               set fname [encoding convertfrom $fname]
-               set diffencoding [get_path_encoding $fname]
                makediffhdr $fname $ids
            } elseif {[string compare -length 3 $line "---"] == 0} {
                # do nothing
  
        } else {
            set line [encoding convertfrom $diffencoding $line]
-           set x [string range $line 0 0]
-           set here [$ctext index "end - 1 chars"]
-           if {$x == "-" || $x == "+"} {
-               set tag [expr {$x == "+"}]
-               $ctext insert end "$line\n" d$tag
-           } elseif {$x == " "} {
-               $ctext insert end "$line\n"
+           # parse the prefix - one ' ', '-' or '+' for each parent
+           set prefix [string range $line 0 [expr {$diffnparents - 1}]]
+           set tag [expr {$diffnparents > 1? "m": "d"}]
+           if {[string trim $prefix " -+"] eq {}} {
+               # prefix only has " ", "-" and "+" in it: normal diff line
+               set num [string first "-" $prefix]
+               if {$num >= 0} {
+                   # removed line, first parent with line is $num
+                   if {$num >= $mergemax} {
+                       set num "max"
+                   }
+                   $ctext insert end "$line\n" $tag$num
+               } else {
+                   set tags {}
+                   if {[string first "+" $prefix] >= 0} {
+                       # added line
+                       lappend tags ${tag}result
+                       if {$diffnparents > 1} {
+                           set num [string first " " $prefix]
+                           if {$num >= 0} {
+                               if {$num >= $mergemax} {
+                                   set num "max"
+                               }
+                               lappend tags m$num
+                           }
+                       }
+                   }
+                   if {$targetline ne {}} {
+                       if {$diffline == $targetline} {
+                           set seehere [$ctext index "end - 1 chars"]
+                           set targetline {}
+                       } else {
+                           incr diffline
+                       }
+                   }
+                   $ctext insert end "$line\n" $tags
+               }
            } else {
                # "\ No newline at end of file",
                # or something else we don't recognize
                $ctext insert end "$line\n" hunksep
            }
-           if {$targetline ne {} && ($x eq " " || $x eq "+")} {
-               if {$diffline == $targetline} {
-                   mark_ctext_line [lindex [split $here .] 0]
-                   set targetline {}
-               } else {
-                   incr diffline
-               }
-           }
        }
      }
+     if {[info exists seehere]} {
+       mark_ctext_line [lindex [split $seehere .] 0]
+     }
      $ctext conf -state disabled
      if {[eof $bdf]} {
        close $bdf
@@@ -7272,7 -7145,7 +7272,7 @@@ proc changediffdisp {} 
      global ctext diffelide
  
      $ctext tag conf d0 -elide [lindex $diffelide 0]
-     $ctext tag conf d1 -elide [lindex $diffelide 1]
+     $ctext tag conf dresult -elide [lindex $diffelide 1]
  }
  
  proc highlightfile {loc cline} {
@@@ -7890,7 -7763,6 +7890,7 @@@ proc mkpatch {} 
      set patchtop $top
      catch {destroy $top}
      toplevel $top
 +    wm transient $top .
      label $top.title -text [mc "Generate patch"]
      grid $top.title - -pady 10
      label $top.from -text [mc "From:"]
      frame $top.buts
      button $top.buts.gen -text [mc "Generate"] -command mkpatchgo
      button $top.buts.can -text [mc "Cancel"] -command mkpatchcan
 +    bind $top <Key-Return> mkpatchgo
 +    bind $top <Key-Escape> 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
@@@ -7957,7 -7827,7 +7957,7 @@@ proc mkpatchgo {} 
      set cmd [lrange $cmd 1 end]
      lappend cmd >$fname &
      if {[catch {eval exec $cmd} err]} {
 -      error_popup "[mc "Error creating patch:"] $err"
 +      error_popup "[mc "Error creating patch:"] $err" $patchtop
      }
      catch {destroy $patchtop}
      unset patchtop
@@@ -7977,7 -7847,6 +7977,7 @@@ proc mktag {} 
      set mktagtop $top
      catch {destroy $top}
      toplevel $top
 +    wm transient $top .
      label $top.title -text [mc "Create tag"]
      grid $top.title - -pady 10
      label $top.id -text [mc "ID:"]
      frame $top.buts
      button $top.buts.gen -text [mc "Create"] -command mktaggo
      button $top.buts.can -text [mc "Cancel"] -command mktagcan
 +    bind $top <Key-Return> mktaggo
 +    bind $top <Key-Escape> 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
@@@ -8010,18 -7877,18 +8010,18 @@@ proc domktag {} 
      set id [$mktagtop.sha1 get]
      set tag [$mktagtop.tag get]
      if {$tag == {}} {
 -      error_popup [mc "No tag name specified"]
 -      return
 +      error_popup [mc "No tag name specified"] $mktagtop
 +      return 0
      }
      if {[info exists tagids($tag)]} {
 -      error_popup [mc "Tag \"%s\" already exists" $tag]
 -      return
 +      error_popup [mc "Tag \"%s\" already exists" $tag] $mktagtop
 +      return 0
      }
      if {[catch {
        exec git tag $tag $id
      } err]} {
 -      error_popup "[mc "Error creating tag:"] $err"
 -      return
 +      error_popup "[mc "Error creating tag:"] $err" $mktagtop
 +      return 0
      }
  
      set tagids($tag) $id
      addedtag $id
      dispneartags 0
      run refill_reflist
 +    return 1
  }
  
  proc redrawtags {id} {
@@@ -8069,7 -7935,7 +8069,7 @@@ proc mktagcan {} 
  }
  
  proc mktaggo {} {
 -    domktag
 +    if {![domktag]} return
      mktagcan
  }
  
@@@ -8080,7 -7946,6 +8080,7 @@@ proc writecommit {} 
      set wrcomtop $top
      catch {destroy $top}
      toplevel $top
 +    wm transient $top .
      label $top.title -text [mc "Write commit to file"]
      grid $top.title - -pady 10
      label $top.id -text [mc "ID:"]
      frame $top.buts
      button $top.buts.gen -text [mc "Write"] -command wrcomgo
      button $top.buts.can -text [mc "Cancel"] -command wrcomcan
 +    bind $top <Key-Return> wrcomgo
 +    bind $top <Key-Escape> 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
@@@ -8118,7 -7981,7 +8118,7 @@@ proc wrcomgo {} 
      set cmd "echo $id | [$wrcomtop.cmd get]"
      set fname [$wrcomtop.fname get]
      if {[catch {exec sh -c $cmd >$fname &} err]} {
 -      error_popup "[mc "Error writing commit:"] $err"
 +      error_popup "[mc "Error writing commit:"] $err" $wrcomtop
      }
      catch {destroy $wrcomtop}
      unset wrcomtop
@@@ -8137,7 -8000,6 +8137,7 @@@ proc mkbranch {} 
      set top .makebranch
      catch {destroy $top}
      toplevel $top
 +    wm transient $top .
      label $top.title -text [mc "Create new branch"]
      grid $top.title - -pady 10
      label $top.id -text [mc "ID:"]
      frame $top.buts
      button $top.buts.go -text [mc "Create"] -command [list mkbrgo $top]
      button $top.buts.can -text [mc "Cancel"] -command "catch {destroy $top}"
 +    bind $top <Key-Return> [list mkbrgo $top]
 +    bind $top <Key-Escape> "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
@@@ -8169,12 -8029,12 +8169,12 @@@ proc mkbrgo {top} 
      set cmdargs {}
      set old_id {}
      if {$name eq {}} {
 -      error_popup [mc "Please specify a name for the new branch"]
 +      error_popup [mc "Please specify a name for the new branch"] $top
        return
      }
      if {[info exists headids($name)]} {
        if {![confirm_popup [mc \
 -              "Branch '%s' already exists. Overwrite?" $name]]} {
 +              "Branch '%s' already exists. Overwrite?" $name] $top]} {
            return
        }
        set old_id $headids($name)
      }
  }
  
 +proc exec_citool {tool_args {baseid {}}} {
 +    global commitinfo env
 +
 +    set save_env [array get env GIT_AUTHOR_*]
 +
 +    if {$baseid ne {}} {
 +      if {![info exists commitinfo($baseid)]} {
 +          getcommit $baseid
 +      }
 +      set author [lindex $commitinfo($baseid) 1]
 +      set date [lindex $commitinfo($baseid) 2]
 +      if {[regexp {^\s*(\S.*\S|\S)\s*<(.*)>\s*$} \
 +                  $author author name email]
 +          && $date ne {}} {
 +          set env(GIT_AUTHOR_NAME) $name
 +          set env(GIT_AUTHOR_EMAIL) $email
 +          set env(GIT_AUTHOR_DATE) $date
 +      }
 +    }
 +
 +    eval exec git citool $tool_args &
 +
 +    array unset env GIT_AUTHOR_*
 +    array set env $save_env
 +}
 +
  proc cherrypick {} {
      global rowmenuid curview
      global mainhead mainheadid
      # no error occurs, and exec takes that as an indication of error...
      if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
        notbusy cherrypick
 -      error_popup $err
 +      if {[regexp -line \
 +               {Entry '(.*)' (would be overwritten by merge|not uptodate)} \
 +               $err msg fname]} {
 +          error_popup [mc "Cherry-pick failed because of local changes\
 +                      to file '%s'.\nPlease commit, reset or stash\
 +                      your changes and try again." $fname]
 +      } elseif {[regexp -line \
 +                     {^(CONFLICT \(.*\):|Automatic cherry-pick failed)} \
 +                     $err]} {
 +          if {[confirm_popup [mc "Cherry-pick failed because of merge\
 +                      conflict.\nDo you wish to run git citool to\
 +                      resolve it?"]]} {
 +              # Force citool to read MERGE_MSG
 +              file delete [file join [gitdir] "GITGUI_MSG"]
 +              exec_citool {} $rowmenuid
 +          }
 +      } else {
 +          error_popup $err
 +      }
 +      run updatecommits
        return
      }
      set newhead [exec git rev-parse HEAD]
@@@ -8323,7 -8138,6 +8323,7 @@@ proc resethead {} 
      button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
      pack $w.ok -side left -fill x -padx 20 -pady 20
      button $w.cancel -text [mc Cancel] -command "destroy $w"
 +    bind $w <Key-Escape> [list destroy $w]
      pack $w.cancel -side right -fill x -padx 20 -pady 20
      bind $w <Visibility> "grab $w; focus $w"
      tkwait window $w
@@@ -8480,7 -8294,6 +8480,7 @@@ proc showrefs {} 
      }
      toplevel $top
      wm title $top [mc "Tags and heads: %s" [file tail [pwd]]]
 +    wm transient $top .
      text $top.list -background $bgcolor -foreground $fgcolor \
        -selectbackground $selectbgcolor -font mainfont \
        -xscrollcommand "$top.xsb set" -yscrollcommand "$top.ysb set" \
      pack $top.f.l -side left
      grid $top.f - -sticky ew -pady 2
      button $top.close -command [list destroy $top] -text [mc "Close"]
 +    bind $top <Key-Escape> [list destroy $top]
      grid $top.close -
      grid columnconfigure $top 0 -weight 1
      grid rowconfigure $top 0 -weight 1
@@@ -9808,7 -9620,6 +9808,7 @@@ proc mkfontdisp {font top which} 
  
  proc choosefont {font which} {
      global fontparam fontlist fonttop fontattr
 +    global prefstop
  
      set fontparam(which) $which
      set fontparam(font) $font
        font create sample
        eval font config sample [font actual $font]
        toplevel $top
 +      wm transient $top $prefstop
        wm title $top [mc "Gitk font chooser"]
        label $top.l -textvariable fontparam(which)
        pack $top.l -side top
        frame $top.buts
        button $top.buts.ok -text [mc "OK"] -command fontok -default active
        button $top.buts.can -text [mc "Cancel"] -command fontcan -default normal
 +      bind $top <Key-Return> fontok
 +      bind $top <Key-Escape> fontcan
        grid $top.buts.ok $top.buts.can
        grid columnconfigure $top.buts 0 -weight 1 -uniform a
        grid columnconfigure $top.buts 1 -weight 1 -uniform a
@@@ -9939,7 -9747,6 +9939,7 @@@ proc doprefs {} 
      }
      toplevel $top
      wm title $top [mc "Gitk preferences"]
 +    wm transient $top .
      label $top.ldisp -text [mc "Commit list display options"]
      grid $top.ldisp - -sticky w -pady 10
      label $top.spacer -text " "
      label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
      button $top.diffnewbut -text [mc "Diff: new lines"] -font optionfont \
        -command [list choosecolor diffcolors 1 $top.diffnew "diff new lines" \
-                     [list $ctext tag conf d1 -foreground]]
+                     [list $ctext tag conf dresult -foreground]]
      grid x $top.diffnewbut $top.diffnew -sticky w
      label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
      button $top.hunksepbut -text [mc "Diff: hunk header"] -font optionfont \
      frame $top.buts
      button $top.buts.ok -text [mc "OK"] -command prefsok -default active
      button $top.buts.can -text [mc "Cancel"] -command prefscan -default normal
 +    bind $top <Key-Return> prefsok
 +    bind $top <Key-Escape> prefscan
      grid $top.buts.ok $top.buts.can
      grid columnconfigure $top.buts 0 -weight 1 -uniform a
      grid columnconfigure $top.buts 1 -weight 1 -uniform a