Code

Merge updated git-gui and gitk that call each other
authorJunio C Hamano <gitster@pobox.com>
Sun, 7 Sep 2008 01:06:39 +0000 (18:06 -0700)
committerJunio C Hamano <gitster@pobox.com>
Sun, 7 Sep 2008 01:06:39 +0000 (18:06 -0700)
Merge git://repo.or.cz/git-gui and git://git.kernel.org/pub/scm/gitk/gitk

    * git://repo.or.cz/git-gui:
      git-gui: Show special diffs for complex conflict cases.
      git-gui: Make F5 reselect a diff, if an untracked file is selected.
      git-gui: Reimplement and enhance auto-selection of diffs.
      git-gui: Support conflict states _U & UT.
      git-gui: Support more merge tools.
      git-gui: Don't allow staging files with conflicts.
      git-gui: Support calling merge tools.
      git-gui: Support resolving conflicts via the diff context menu.
      git-gui: Mark forgotten strings for translation.
      git-gui: Allow specifying an initial line for git gui blame.
      git-gui: Better positioning in Blame Parent Commit
      git-gui: Support passing blame to a parent commit.
      git-gui: Support starting gitk from Gui Blame
      git-gui: Teach git gui about file type changes

    * git://git.kernel.org/pub/scm/gitk/gitk:
      gitk: Add menu item for calling git gui blame
      gitk: Add option to specify the default commit on command line

git-gui/git-gui.sh
git-gui/lib/blame.tcl
git-gui/lib/browser.tcl
git-gui/lib/commit.tcl
git-gui/lib/diff.tcl
git-gui/lib/index.tcl
git-gui/lib/mergetool.tcl [new file with mode: 0644]
git-gui/lib/option.tcl
gitk-git/gitk

index 86402d49f72c56d793f2f1958a9e3d3f8300f367..10d8a4422f151eda78a24ff533eb20c3d775d099 100755 (executable)
@@ -657,6 +657,8 @@ proc apply_config {} {
 }
 
 set default_config(branch.autosetupmerge) true
+set default_config(merge.tool) {}
+set default_config(merge.keepbackup) true
 set default_config(merge.diffstat) true
 set default_config(merge.summary) false
 set default_config(merge.verbosity) 2
@@ -668,6 +670,7 @@ set default_config(gui.pruneduringfetch) false
 set default_config(gui.trustmtime) false
 set default_config(gui.fastcopyblame) false
 set default_config(gui.copyblamethreshold) 40
+set default_config(gui.blamehistoryctx) 7
 set default_config(gui.diffcontext) 5
 set default_config(gui.commitmsgwidth) 75
 set default_config(gui.newbranchtemplate) {}
@@ -1323,6 +1326,8 @@ proc rescan_done {fd buf after} {
        unlock_index
        display_all_files
        if {$current_diff_path ne {}} reshow_diff
+       if {$current_diff_path eq {}} select_first_diff
+
        uplevel #0 $after
 }
 
@@ -1619,6 +1624,15 @@ static unsigned char file_merge_bits[] = {
    0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
 } -maskdata $filemask
 
+image create bitmap file_statechange -background white -foreground green -data {
+#define file_merge_width 14
+#define file_merge_height 15
+static unsigned char file_statechange_bits[] = {
+   0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x62, 0x10,
+   0x62, 0x10, 0xba, 0x11, 0xba, 0x11, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10,
+   0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
+} -maskdata $filemask
+
 set ui_index .vpane.files.index.list
 set ui_workdir .vpane.files.workdir.list
 
@@ -1627,12 +1641,14 @@ set all_icons(A$ui_index)   file_fulltick
 set all_icons(M$ui_index)   file_fulltick
 set all_icons(D$ui_index)   file_removed
 set all_icons(U$ui_index)   file_merge
+set all_icons(T$ui_index)   file_statechange
 
 set all_icons(_$ui_workdir) file_plain
 set all_icons(M$ui_workdir) file_mod
 set all_icons(D$ui_workdir) file_question
 set all_icons(U$ui_workdir) file_merge
 set all_icons(O$ui_workdir) file_plain
+set all_icons(T$ui_workdir) file_statechange
 
 set max_status_desc 0
 foreach i {
@@ -1643,6 +1659,9 @@ foreach i {
                {MM {mc "Portions staged for commit"}}
                {MD {mc "Staged for commit, missing"}}
 
+               {_T {mc "File type changed, not staged"}}
+               {T_ {mc "File type changed, staged"}}
+
                {_O {mc "Untracked, not staged"}}
                {A_ {mc "Staged for commit"}}
                {AM {mc "Portions staged for commit"}}
@@ -1652,10 +1671,12 @@ foreach i {
                {D_ {mc "Staged for removal"}}
                {DO {mc "Staged for removal, still present"}}
 
+               {_U {mc "Requires merge resolution"}}
                {U_ {mc "Requires merge resolution"}}
                {UU {mc "Requires merge resolution"}}
                {UM {mc "Requires merge resolution"}}
                {UD {mc "Requires merge resolution"}}
+               {UT {mc "Requires merge resolution"}}
        } {
        set text [eval [lindex $i 1]]
        if {$max_status_desc < [string length $text]} {
@@ -1796,13 +1817,120 @@ proc do_rescan {} {
        rescan ui_ready
 }
 
+proc ui_do_rescan {} {
+       rescan {force_first_diff; ui_ready}
+}
+
 proc do_commit {} {
        commit_tree
 }
 
 proc next_diff {} {
        global next_diff_p next_diff_w next_diff_i
-       show_diff $next_diff_p $next_diff_w $next_diff_i
+       show_diff $next_diff_p $next_diff_w {}
+}
+
+proc find_anchor_pos {lst name} {
+       set lid [lsearch -sorted -exact $lst $name]
+
+       if {$lid == -1} {
+               set lid 0
+               foreach lname $lst {
+                       if {$lname >= $name} break
+                       incr lid
+               }
+       }
+
+       return $lid
+}
+
+proc find_file_from {flist idx delta path mmask} {
+       global file_states
+
+       set len [llength $flist]
+       while {$idx >= 0 && $idx < $len} {
+               set name [lindex $flist $idx]
+
+               if {$name ne $path && [info exists file_states($name)]} {
+                       set state [lindex $file_states($name) 0]
+
+                       if {$mmask eq {} || [regexp $mmask $state]} {
+                               return $idx
+                       }
+               }
+
+               incr idx $delta
+       }
+
+       return {}
+}
+
+proc find_next_diff {w path {lno {}} {mmask {}}} {
+       global next_diff_p next_diff_w next_diff_i
+       global file_lists ui_index ui_workdir
+
+       set flist $file_lists($w)
+       if {$lno eq {}} {
+               set lno [find_anchor_pos $flist $path]
+       } else {
+               incr lno -1
+       }
+
+       if {$mmask ne {} && ![regexp {(^\^)|(\$$)} $mmask]} {
+               if {$w eq $ui_index} {
+                       set mmask "^$mmask"
+               } else {
+                       set mmask "$mmask\$"
+               }
+       }
+
+       set idx [find_file_from $flist $lno 1 $path $mmask]
+       if {$idx eq {}} {
+               incr lno -1
+               set idx [find_file_from $flist $lno -1 $path $mmask]
+       }
+
+       if {$idx ne {}} {
+               set next_diff_w $w
+               set next_diff_p [lindex $flist $idx]
+               set next_diff_i [expr {$idx+1}]
+               return 1
+       } else {
+               return 0
+       }
+}
+
+proc next_diff_after_action {w path {lno {}} {mmask {}}} {
+       global current_diff_path
+
+       if {$path ne $current_diff_path} {
+               return {}
+       } elseif {[find_next_diff $w $path $lno $mmask]} {
+               return {next_diff;}
+       } else {
+               return {reshow_diff;}
+       }
+}
+
+proc select_first_diff {} {
+       global ui_workdir
+
+       if {[find_next_diff $ui_workdir {} 1 {^_?U}] ||
+           [find_next_diff $ui_workdir {} 1 {[^O]$}]} {
+               next_diff
+       }
+}
+
+proc force_first_diff {} {
+       global current_diff_path
+
+       if {[info exists file_states($current_diff_path)]} {
+               set state [lindex $file_states($current_diff_path) 0]
+
+               if {[string index $state 1] ne {O}} return
+       }
+
+       select_first_diff
 }
 
 proc toggle_or_diff {w x y} {
@@ -1823,34 +1951,27 @@ proc toggle_or_diff {w x y} {
        $ui_index tag remove in_sel 0.0 end
        $ui_workdir tag remove in_sel 0.0 end
 
-       if {$col == 0 && $y > 1} {
-               set i [expr {$lno-1}]
-               set ll [expr {[llength $file_lists($w)]-1}]
-
-               if {$i == $ll && $i == 0} {
-                       set after {reshow_diff;}
-               } else {
-                       global next_diff_p next_diff_w next_diff_i
-
-                       set next_diff_w $w
-
-                       if {$i < $ll} {
-                               set i [expr {$i + 1}]
-                               set next_diff_i $i
-                       } else {
-                               set next_diff_i $i
-                               set i [expr {$i - 1}]
-                       }
+       # Do not stage files with conflicts
+       if {[info exists file_states($path)]} {
+               set state [lindex $file_states($path) 0]
+       } else {
+               set state {__}
+       }
 
-                       set next_diff_p [lindex $file_lists($w) $i]
+       if {[string first {U} $state] >= 0} {
+               set col 1
+       }
 
-                       if {$next_diff_p ne {} && $current_diff_path ne {}} {
-                               set after {next_diff;}
-                       } else {
-                               set after {}
-                       }
+       # Restage the file, or simply show the diff
+       if {$col == 0 && $y > 1} {
+               if {[string index $state 1] eq {O}} {
+                       set mmask {}
+               } else {
+                       set mmask {[^O]}
                }
 
+               set after [next_diff_after_action $w $path $lno $mmask]
+
                if {$w eq $ui_index} {
                        update_indexinfo \
                                "Unstaging [short_path $path] from commit" \
@@ -2113,7 +2234,7 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} {
        .mbar.commit add separator
 
        .mbar.commit add command -label [mc Rescan] \
-               -command do_rescan \
+               -command ui_do_rescan \
                -accelerator F5
        lappend disable_on_lock \
                [list .mbar.commit entryconf [.mbar.commit index last] -state]
@@ -2281,10 +2402,15 @@ proc usage {} {
 switch -- $subcommand {
 browser -
 blame {
-       set subcommand_args {rev? path}
+       if {$subcommand eq "blame"} {
+               set subcommand_args {[--line=<num>] rev? path}
+       } else {
+               set subcommand_args {rev? path}
+       }
        if {$argv eq {}} usage
        set head {}
        set path {}
+       set jump_spec {}
        set is_path 0
        foreach a $argv {
                if {$is_path || [file exists $_prefix$a]} {
@@ -2298,6 +2424,9 @@ blame {
                                set path {}
                        }
                        set is_path 1
+               } elseif {[regexp {^--line=(\d+)$} $a a lnum]} {
+                       if {$jump_spec ne {} || $head ne {}} usage
+                       set jump_spec [list $lnum]
                } elseif {$head eq {}} {
                        if {$head ne {}} usage
                        set head $a
@@ -2329,6 +2458,7 @@ blame {
 
        switch -- $subcommand {
        browser {
+               if {$jump_spec ne {}} usage
                if {$head eq {}} {
                        if {$path ne {} && [file isdirectory $path]} {
                                set head $current_branch
@@ -2344,7 +2474,7 @@ blame {
                        puts stderr [mc "fatal: cannot stat path %s: No such file or directory" $path]
                        exit 1
                }
-               blame::new $head $path
+               blame::new $head $path $jump_spec
        }
        }
        return
@@ -2460,7 +2590,7 @@ pack .vpane.lower.commarea.buttons.l -side top -fill x
 pack .vpane.lower.commarea.buttons -side left -fill y
 
 button .vpane.lower.commarea.buttons.rescan -text [mc Rescan] \
-       -command do_rescan
+       -command ui_do_rescan
 pack .vpane.lower.commarea.buttons.rescan -side top -fill x
 lappend disable_on_lock \
        {.vpane.lower.commarea.buttons.rescan conf -state}
@@ -2689,6 +2819,51 @@ $ui_diff tag raise sel
 
 # -- Diff Body Context Menu
 #
+
+proc create_common_diff_popup {ctxm} {
+       $ctxm add command \
+               -label [mc "Show Less Context"] \
+               -command show_less_context
+       lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
+       $ctxm add command \
+               -label [mc "Show More Context"] \
+               -command show_more_context
+       lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
+       $ctxm add separator
+       $ctxm add command \
+               -label [mc Refresh] \
+               -command reshow_diff
+       lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
+       $ctxm add command \
+               -label [mc Copy] \
+               -command {tk_textCopy $ui_diff}
+       lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
+       $ctxm add command \
+               -label [mc "Select All"] \
+               -command {focus $ui_diff;$ui_diff tag add sel 0.0 end}
+       lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
+       $ctxm add command \
+               -label [mc "Copy All"] \
+               -command {
+                       $ui_diff tag add sel 0.0 end
+                       tk_textCopy $ui_diff
+                       $ui_diff tag remove sel 0.0 end
+               }
+       lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
+       $ctxm add separator
+       $ctxm add command \
+               -label [mc "Decrease Font Size"] \
+               -command {incr_font_size font_diff -1}
+       lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
+       $ctxm add command \
+               -label [mc "Increase Font Size"] \
+               -command {incr_font_size font_diff 1}
+       lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
+       $ctxm add separator
+       $ctxm add command -label [mc "Options..."] \
+               -command do_options
+}
+
 set ctxm .vpane.lower.diff.body.ctxm
 menu $ctxm -tearoff 0
 $ctxm add command \
@@ -2702,71 +2877,65 @@ $ctxm add command \
 set ui_diff_applyline [$ctxm index last]
 lappend diff_actions [list $ctxm entryconf $ui_diff_applyline -state]
 $ctxm add separator
-$ctxm add command \
-       -label [mc "Show Less Context"] \
-       -command show_less_context
-lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
-$ctxm add command \
-       -label [mc "Show More Context"] \
-       -command show_more_context
-lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
-$ctxm add separator
-$ctxm add command \
-       -label [mc Refresh] \
-       -command reshow_diff
-lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
-$ctxm add command \
-       -label [mc Copy] \
-       -command {tk_textCopy $ui_diff}
-lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
-$ctxm add command \
-       -label [mc "Select All"] \
-       -command {focus $ui_diff;$ui_diff tag add sel 0.0 end}
-lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
-$ctxm add command \
-       -label [mc "Copy All"] \
-       -command {
-               $ui_diff tag add sel 0.0 end
-               tk_textCopy $ui_diff
-               $ui_diff tag remove sel 0.0 end
-       }
-lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
-$ctxm add separator
-$ctxm add command \
-       -label [mc "Decrease Font Size"] \
-       -command {incr_font_size font_diff -1}
-lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
-$ctxm add command \
-       -label [mc "Increase Font Size"] \
-       -command {incr_font_size font_diff 1}
-lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
-$ctxm add separator
-$ctxm add command -label [mc "Options..."] \
-       -command do_options
-proc popup_diff_menu {ctxm x y X Y} {
+create_common_diff_popup $ctxm
+
+set ctxmmg .vpane.lower.diff.body.ctxmmg
+menu $ctxmmg -tearoff 0
+$ctxmmg add command \
+       -label [mc "Run Merge Tool"] \
+       -command {merge_resolve_tool}
+lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
+$ctxmmg add separator
+$ctxmmg add command \
+       -label [mc "Use Remote Version"] \
+       -command {merge_resolve_one 3}
+lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
+$ctxmmg add command \
+       -label [mc "Use Local Version"] \
+       -command {merge_resolve_one 2}
+lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
+$ctxmmg add command \
+       -label [mc "Revert To Base"] \
+       -command {merge_resolve_one 1}
+lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
+$ctxmmg add separator
+create_common_diff_popup $ctxmmg
+
+proc popup_diff_menu {ctxm ctxmmg x y X Y} {
        global current_diff_path file_states
        set ::cursorX $x
        set ::cursorY $y
-       if {$::ui_index eq $::current_diff_side} {
-               set l [mc "Unstage Hunk From Commit"]
-               set t [mc "Unstage Line From Commit"]
+       if {[info exists file_states($current_diff_path)]} {
+               set state [lindex $file_states($current_diff_path) 0]
        } else {
-               set l [mc "Stage Hunk For Commit"]
-               set t [mc "Stage Line For Commit"]
-       }
-       if {$::is_3way_diff
-               || $current_diff_path eq {}
-               || ![info exists file_states($current_diff_path)]
-               || {_O} eq [lindex $file_states($current_diff_path) 0]} {
-               set s disabled
+               set state {__}
+       }
+       if {[string first {U} $state] >= 0} {
+               tk_popup $ctxmmg $X $Y
        } else {
-               set s normal
+               if {$::ui_index eq $::current_diff_side} {
+                       set l [mc "Unstage Hunk From Commit"]
+                       set t [mc "Unstage Line From Commit"]
+               } else {
+                       set l [mc "Stage Hunk For Commit"]
+                       set t [mc "Stage Line For Commit"]
+               }
+               if {$::is_3way_diff
+                       || $current_diff_path eq {}
+                       || {__} eq $state
+                       || {_O} eq $state
+                       || {_T} eq $state
+                       || {T_} eq $state} {
+                       set s disabled
+               } else {
+                       set s normal
+               }
+               $ctxm entryconf $::ui_diff_applyhunk -state $s -label $l
+               $ctxm entryconf $::ui_diff_applyline -state $s -label $t
+               tk_popup $ctxm $X $Y
        }
-       $ctxm entryconf $::ui_diff_applyhunk -state $s -label $l
-       $ctxm entryconf $::ui_diff_applyline -state $s -label $t
-       tk_popup $ctxm $X $Y
 }
-bind_button3 $ui_diff [list popup_diff_menu $ctxm %x %y %X %Y]
+bind_button3 $ui_diff [list popup_diff_menu $ctxm $ctxmmg %x %y %X %Y]
 
 # -- Status Bar
 #
@@ -2842,9 +3011,9 @@ if {[is_enabled transport]} {
        bind . <$M1B-Key-P> do_push_anywhere
 }
 
-bind .   <Key-F5>     do_rescan
-bind .   <$M1B-Key-r> do_rescan
-bind .   <$M1B-Key-R> do_rescan
+bind .   <Key-F5>     ui_do_rescan
+bind .   <$M1B-Key-r> ui_do_rescan
+bind .   <$M1B-Key-R> ui_do_rescan
 bind .   <$M1B-Key-s> do_signoff
 bind .   <$M1B-Key-S> do_signoff
 bind .   <$M1B-Key-t> do_add_selection
index b6e42cbc8fe0a49c301335f78cc2941bd9d59870..827c85d67f0b0f18d22b1399624e6819cf93f23f 100644 (file)
@@ -58,7 +58,7 @@ field tooltip_t         {} ; # Text widget in $tooltip_wm
 field tooltip_timer     {} ; # Current timer event for our tooltip
 field tooltip_commit    {} ; # Commit(s) in tooltip
 
-constructor new {i_commit i_path} {
+constructor new {i_commit i_path i_jump} {
        global cursor_ptr
        variable active_color
        variable group_colors
@@ -259,6 +259,12 @@ constructor new {i_commit i_path} {
        $w.ctxm add command \
                -label [mc "Do Full Copy Detection"] \
                -command [cb _fullcopyblame]
+       $w.ctxm add command \
+               -label [mc "Show History Context"] \
+               -command [cb _gitkcommit]
+       $w.ctxm add command \
+               -label [mc "Blame Parent Commit"] \
+               -command [cb _blameparent]
 
        foreach i $w_columns {
                for {set g 0} {$g < [llength $group_colors]} {incr g} {
@@ -332,7 +338,7 @@ constructor new {i_commit i_path} {
        wm protocol $top WM_DELETE_WINDOW "destroy $top"
        bind $top <Destroy> [cb _kill]
 
-       _load $this {}
+       _load $this $i_jump
 }
 
 method _kill {} {
@@ -787,19 +793,27 @@ method _load_commit {cur_w cur_d pos} {
        set lno [lindex [split [$cur_w index $pos] .] 0]
        set dat [lindex $line_data $lno]
        if {$dat ne {}} {
-               lappend history [list \
-                       $commit $path \
-                       $highlight_column \
-                       $highlight_line \
-                       [lindex [$w_file xview] 0] \
-                       [lindex [$w_file yview] 0] \
-                       ]
-               set commit [lindex $dat 0]
-               set path   [lindex $dat 1]
-               _load $this [list [lindex $dat 2]]
+               _load_new_commit $this  \
+                       [lindex $dat 0] \
+                       [lindex $dat 1] \
+                       [list [lindex $dat 2]]
        }
 }
 
+method _load_new_commit {new_commit new_path jump} {
+       lappend history [list \
+               $commit $path \
+               $highlight_column \
+               $highlight_line \
+               [lindex [$w_file xview] 0] \
+               [lindex [$w_file yview] 0] \
+               ]
+
+       set commit $new_commit
+       set path   $new_path
+       _load $this $jump
+}
+
 method _showcommit {cur_w lno} {
        global repo_config
        variable active_color
@@ -905,10 +919,14 @@ method _showcommit {cur_w lno} {
        }
 }
 
-method _copycommit {} {
+method _get_click_amov_info {} {
        set pos @$::cursorX,$::cursorY
        set lno [lindex [split [$::cursorW index $pos] .] 0]
-       set dat [lindex $amov_data $lno]
+       return [lindex $amov_data $lno]
+}
+
+method _copycommit {} {
+       set dat [_get_click_amov_info $this]
        if {$dat ne {}} {
                clipboard clear
                clipboard append \
@@ -918,6 +936,124 @@ method _copycommit {} {
        }
 }
 
+method _format_offset_date {base offset} {
+       set exval [expr {$base + $offset*24*60*60}]
+       return [clock format $exval -format {%Y-%m-%d}]
+}
+
+method _gitkcommit {} {
+       set dat [_get_click_amov_info $this]
+       if {$dat ne {}} {
+               set cmit [lindex $dat 0]
+               set radius [get_config gui.blamehistoryctx]
+               set cmdline [list --select-commit=$cmit]
+
+                if {$radius > 0} {
+                       set author_time {}
+                       set committer_time {}
+
+                       catch {set author_time $header($cmit,author-time)}
+                       catch {set committer_time $header($cmit,committer-time)}
+
+                       if {$committer_time eq {}} {
+                               set committer_time $author_time
+                       }
+
+                       set after_time [_format_offset_date $this $committer_time [expr {-$radius}]]
+                       set before_time [_format_offset_date $this $committer_time $radius]
+
+                       lappend cmdline --after=$after_time --before=$before_time
+               }
+
+               lappend cmdline $cmit
+
+               set base_rev "HEAD"
+               if {$commit ne {}} {
+                       set base_rev $commit
+               }
+
+               if {$base_rev ne $cmit} {
+                       lappend cmdline $base_rev
+               }
+
+               do_gitk $cmdline
+       }
+}
+
+method _blameparent {} {
+       set dat [_get_click_amov_info $this]
+       if {$dat ne {}} {
+               set cmit [lindex $dat 0]
+               set new_path [lindex $dat 1]
+
+               if {[catch {set cparent [git rev-parse --verify "$cmit^"]}]} {
+                       error_popup [strcat [mc "Cannot find parent commit:"] "\n\n$err"]
+                       return;
+               }
+
+               _kill $this
+
+               # Generate a diff between the commit and its parent,
+               # and use the hunks to update the line number.
+               # Request zero context to simplify calculations.
+               if {[catch {set fd [eval git_read diff-tree \
+                               --unified=0 $cparent $cmit $new_path]} err]} {
+                       $status stop [mc "Unable to display parent"]
+                       error_popup [strcat [mc "Error loading diff:"] "\n\n$err"]
+                       return
+               }
+
+               set r_orig_line [lindex $dat 2]
+
+               fconfigure $fd \
+                       -blocking 0 \
+                       -encoding binary \
+                       -translation binary
+               fileevent $fd readable [cb _read_diff_load_commit \
+                       $fd $cparent $new_path $r_orig_line]
+               set current_fd $fd
+       }
+}
+
+method _read_diff_load_commit {fd cparent new_path tline} {
+       if {$fd ne $current_fd} {
+               catch {close $fd}
+               return
+       }
+
+       while {[gets $fd line] >= 0} {
+               if {[regexp {^@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@} $line line \
+                       old_line osz old_size new_line nsz new_size]} {
+
+                       if {$osz eq {}} { set old_size 1 }
+                       if {$nsz eq {}} { set new_size 1 }
+
+                       if {$new_line <= $tline} {
+                               if {[expr {$new_line + $new_size}] > $tline} {
+                                       # Target line within the hunk
+                                       set line_shift [expr {
+                                               ($new_size-$old_size)*($tline-$new_line)/$new_size
+                                               }]
+                               } else {
+                                       set line_shift [expr {$new_size-$old_size}]
+                               }
+
+                               set r_orig_line [expr {$r_orig_line - $line_shift}]
+                       }
+               }
+       }
+
+       if {[eof $fd]} {
+               close $fd;
+               set current_fd {}
+
+               _load_new_commit $this  \
+                       $cparent        \
+                       $new_path       \
+                       [list $r_orig_line]
+       }
+} ifdeleted { catch {close $fd} }
+
 method _show_tooltip {cur_w pos} {
        if {$tooltip_wm ne {}} {
                _open_tooltip $this $cur_w
index ab470d12648c1dd3560b411e70ea210cc4381e3e..0410cc68df186d6e8e0080058be94126243fd4d3 100644 (file)
@@ -151,7 +151,7 @@ method _enter {} {
                                append p [lindex $n 1]
                        }
                        append p $name
-                       blame::new $browser_commit $p
+                       blame::new $browser_commit $p {}
                }
                }
        }
index 40a710355751836e78b65101592b753266f507ca..2977315624fc5deeecff275d8dc8b3f2e3210a70 100644 (file)
@@ -149,7 +149,9 @@ The rescan will be automatically started now.
                _? {continue}
                A? -
                D? -
+               T_ -
                M? {set files_ready 1}
+               _U -
                U? {
                        error_popup [mc "Unmerged files cannot be committed.
 
@@ -428,6 +430,7 @@ A rescan will be automatically started now.
                __ -
                A_ -
                M_ -
+               T_ -
                D_ {
                        unset file_states($path)
                        catch {unset selected_paths($path)}
index 1970b601e1ce34af120cfc3ee16e1b2c26c6b19d..a30c80a935b58ce0c783dfffde196a85eadca9a2 100644 (file)
@@ -24,10 +24,16 @@ proc reshow_diff {} {
        set p $current_diff_path
        if {$p eq {}} {
                # No diff is being shown.
-       } elseif {$current_diff_side eq {}
-               || [catch {set s $file_states($p)}]
-               || [lsearch -sorted -exact $file_lists($current_diff_side) $p] == -1} {
+       } elseif {$current_diff_side eq {}} {
                clear_diff
+       } elseif {[catch {set s $file_states($p)}]
+               || [lsearch -sorted -exact $file_lists($current_diff_side) $p] == -1} {
+
+               if {[find_next_diff $current_diff_side $p {} {[^O]}]} {
+                       next_diff
+               } else {
+                       clear_diff
+               }
        } else {
                set save_pos [lindex [$ui_diff yview] 0]
                show_diff $p $current_diff_side {} $save_pos
@@ -59,6 +65,7 @@ proc show_diff {path w {lno {}} {scroll_pos {}}} {
        global is_3way_diff diff_active repo_config
        global ui_diff ui_index ui_workdir
        global current_diff_path current_diff_side current_diff_header
+       global current_diff_queue
 
        if {$diff_active || ![lock_index read]} return
 
@@ -71,17 +78,74 @@ proc show_diff {path w {lno {}} {scroll_pos {}}} {
        }
        if {$lno >= 1} {
                $w tag add in_diff $lno.0 [expr {$lno + 1}].0
+               $w see $lno.0
        }
 
        set s $file_states($path)
        set m [lindex $s 0]
-       set is_3way_diff 0
-       set diff_active 1
        set current_diff_path $path
        set current_diff_side $w
-       set current_diff_header {}
+       set current_diff_queue {}
        ui_status [mc "Loading diff of %s..." [escape_path $path]]
 
+       if {[string first {U} $m] >= 0} {
+               merge_load_stages $path [list show_unmerged_diff $scroll_pos]
+       } elseif {$m eq {_O}} {
+               show_other_diff $path $w $m $scroll_pos
+       } else {
+               start_show_diff $scroll_pos
+       }
+}
+
+proc show_unmerged_diff {scroll_pos} {
+       global current_diff_path current_diff_side
+       global merge_stages ui_diff
+       global current_diff_queue
+
+       if {$merge_stages(2) eq {}} {
+               lappend current_diff_queue \
+                       [list "LOCAL: deleted\nREMOTE:\n" d======= \
+                           [list ":1:$current_diff_path" ":3:$current_diff_path"]]
+       } elseif {$merge_stages(3) eq {}} {
+               lappend current_diff_queue \
+                       [list "REMOTE: deleted\nLOCAL:\n" d======= \
+                           [list ":1:$current_diff_path" ":2:$current_diff_path"]]
+       } elseif {[lindex $merge_stages(1) 0] eq {120000}
+               || [lindex $merge_stages(2) 0] eq {120000}
+               || [lindex $merge_stages(3) 0] eq {120000}} {
+               lappend current_diff_queue \
+                       [list "LOCAL:\n" d======= \
+                           [list ":1:$current_diff_path" ":2:$current_diff_path"]]
+               lappend current_diff_queue \
+                       [list "REMOTE:\n" d======= \
+                           [list ":1:$current_diff_path" ":3:$current_diff_path"]]
+       } else {
+               start_show_diff $scroll_pos
+               return
+       }
+
+       advance_diff_queue $scroll_pos
+}
+
+proc advance_diff_queue {scroll_pos} {
+       global current_diff_queue ui_diff
+
+       set item [lindex $current_diff_queue 0]
+       set current_diff_queue [lrange $current_diff_queue 1 end]
+
+       $ui_diff conf -state normal
+       $ui_diff insert end [lindex $item 0] [lindex $item 1]
+       $ui_diff conf -state disabled
+
+       start_show_diff $scroll_pos [lindex $item 2]
+}
+
+proc show_other_diff {path w m scroll_pos} {
+       global file_states file_lists
+       global is_3way_diff diff_active repo_config
+       global ui_diff ui_index ui_workdir
+       global current_diff_path current_diff_side current_diff_header
+
        # - Git won't give us the diff, there's nothing to compare to!
        #
        if {$m eq {_O}} {
@@ -160,13 +224,29 @@ proc show_diff {path w {lno {}} {scroll_pos {}}} {
                ui_ready
                return
        }
+}
+
+proc start_show_diff {scroll_pos {add_opts {}}} {
+       global file_states file_lists
+       global is_3way_diff diff_active repo_config
+       global ui_diff ui_index ui_workdir
+       global current_diff_path current_diff_side current_diff_header
+
+       set path $current_diff_path
+       set w $current_diff_side
+
+       set s $file_states($path)
+       set m [lindex $s 0]
+       set is_3way_diff 0
+       set diff_active 1
+       set current_diff_header {}
 
        set cmd [list]
        if {$w eq $ui_index} {
                lappend cmd diff-index
                lappend cmd --cached
        } elseif {$w eq $ui_workdir} {
-               if {[string index $m 0] eq {U}} {
+               if {[string first {U} $m] >= 0} {
                        lappend cmd diff
                } else {
                        lappend cmd diff-files
@@ -181,8 +261,12 @@ proc show_diff {path w {lno {}} {scroll_pos {}}} {
        if {$w eq $ui_index} {
                lappend cmd [PARENT]
        }
-       lappend cmd --
-       lappend cmd $path
+       if {$add_opts ne {}} {
+               eval lappend cmd $add_opts
+       } else {
+               lappend cmd --
+               lappend cmd $path
+       }
 
        if {[catch {set fd [eval git_read --nice $cmd]} err]} {
                set diff_active 0
@@ -203,6 +287,7 @@ proc show_diff {path w {lno {}} {scroll_pos {}}} {
 proc read_diff {fd scroll_pos} {
        global ui_diff diff_active
        global is_3way_diff current_diff_header
+       global current_diff_queue
 
        $ui_diff conf -state normal
        while {[gets $fd line] >= 0} {
@@ -290,6 +375,12 @@ proc read_diff {fd scroll_pos} {
 
        if {[eof $fd]} {
                close $fd
+
+               if {$current_diff_queue ne {}} {
+                       advance_diff_queue $scroll_pos
+                       return
+               }
+
                set diff_active 0
                unlock_index
                if {$scroll_pos ne {}} {
@@ -370,10 +461,9 @@ proc apply_hunk {x y} {
        }
        unlock_index
        display_file $current_diff_path $mi
+       # This should trigger shift to the next changed file
        if {$o eq {_}} {
-               clear_diff
-       } else {
-               set current_diff_path $current_diff_path
+               reshow_diff
        }
 }
 
index 3c1fce7475d362d1880d915ff4bdf168fda28593..b045219a1cca6bf46218bf211c0c88b8d699e144 100644 (file)
@@ -99,6 +99,7 @@ proc write_update_indexinfo {fd pathList totalCnt batch after} {
                switch -glob -- [lindex $s 0] {
                A? {set new _O}
                M? {set new _M}
+               T_ {set new _T}
                D_ {set new _D}
                D? {set new _?}
                ?? {continue}
@@ -162,6 +163,8 @@ proc write_update_index {fd pathList totalCnt batch after} {
                ?D {set new D_}
                _O -
                AM {set new A_}
+               _T {set new T_}
+               _U -
                U? {
                        if {[file exists $path]} {
                                set new M_
@@ -231,6 +234,7 @@ proc write_checkout_index {fd pathList totalCnt batch after} {
                switch -glob -- [lindex $file_states($path) 0] {
                U? {continue}
                ?M -
+               ?T -
                ?D {
                        puts -nonewline $fd "[encoding convertto $path]\0"
                        display_file $path ?_
@@ -252,6 +256,7 @@ proc unstage_helper {txt paths} {
                switch -glob -- [lindex $file_states($path) 0] {
                A? -
                M? -
+               T_ -
                D? {
                        lappend pathList $path
                        if {$path eq $current_diff_path} {
@@ -296,6 +301,7 @@ proc add_helper {txt paths} {
                _O -
                ?M -
                ?D -
+               ?T -
                U? {
                        lappend pathList $path
                        if {$path eq $current_diff_path} {
@@ -336,6 +342,7 @@ proc do_add_all {} {
                switch -glob -- [lindex $file_states($path) 0] {
                U? {continue}
                ?M -
+               ?T -
                ?D {lappend paths $path}
                }
        }
@@ -353,6 +360,7 @@ proc revert_helper {txt paths} {
                switch -glob -- [lindex $file_states($path) 0] {
                U? {continue}
                ?M -
+               ?T -
                ?D {
                        lappend pathList $path
                        if {$path eq $current_diff_path} {
@@ -409,11 +417,11 @@ proc do_revert_selection {} {
 
        if {[array size selected_paths] > 0} {
                revert_helper \
-                       {Reverting selected files} \
+                       [mc "Reverting selected files"] \
                        [array names selected_paths]
        } elseif {$current_diff_path ne {}} {
                revert_helper \
-                       "Reverting [short_path $current_diff_path]" \
+                       [mc "Reverting %s" [short_path $current_diff_path]] \
                        [list $current_diff_path]
        }
 }
diff --git a/git-gui/lib/mergetool.tcl b/git-gui/lib/mergetool.tcl
new file mode 100644 (file)
index 0000000..79c58bc
--- /dev/null
@@ -0,0 +1,373 @@
+# git-gui merge conflict resolution
+# parts based on git-mergetool (c) 2006 Theodore Y. Ts'o
+
+proc merge_resolve_one {stage} {
+       global current_diff_path
+
+       switch -- $stage {
+               1 { set target [mc "the base version"] }
+               2 { set target [mc "this branch"] }
+               3 { set target [mc "the other branch"] }
+       }
+
+       set op_question [mc "Force resolution to %s?
+Note that the diff shows only conflicting changes.
+
+%s will be overwritten.
+
+This operation can be undone only by restarting the merge." \
+               $target [short_path $current_diff_path]]
+
+       if {[ask_popup $op_question] eq {yes}} {
+               merge_load_stages $current_diff_path [list merge_force_stage $stage]
+       }
+}
+
+proc merge_add_resolution {path} {
+       global current_diff_path ui_workdir
+
+       set after [next_diff_after_action $ui_workdir $path {} {^_?U}]
+
+       update_index \
+               [mc "Adding resolution for %s" [short_path $path]] \
+               [list $path] \
+               [concat $after [list ui_ready]]
+}
+
+proc merge_force_stage {stage} {
+       global current_diff_path merge_stages
+
+       if {$merge_stages($stage) ne {}} {
+               git checkout-index -f --stage=$stage -- $current_diff_path
+       } else {
+               file delete -- $current_diff_path
+       }
+
+       merge_add_resolution $current_diff_path
+}
+
+proc merge_load_stages {path cont} {
+       global merge_stages_fd merge_stages merge_stages_buf
+
+       if {[info exists merge_stages_fd]} {
+               catch { kill_file_process $merge_stages_fd }
+               catch { close $merge_stages_fd }
+       }
+
+       set merge_stages(0) {}
+       set merge_stages(1) {}
+       set merge_stages(2) {}
+       set merge_stages(3) {}
+       set merge_stages_buf {}
+
+       set merge_stages_fd [eval git_read ls-files -u -z -- $path]
+
+       fconfigure $merge_stages_fd -blocking 0 -translation binary -encoding binary
+       fileevent $merge_stages_fd readable [list read_merge_stages $merge_stages_fd $cont]
+}
+
+proc read_merge_stages {fd cont} {
+       global merge_stages_buf merge_stages_fd merge_stages
+
+       append merge_stages_buf [read $fd]
+       set pck [split $merge_stages_buf "\0"]
+       set merge_stages_buf [lindex $pck end]
+
+       if {[eof $fd] && $merge_stages_buf ne {}} {
+               lappend pck {}
+               set merge_stages_buf {}
+       }
+
+       foreach p [lrange $pck 0 end-1] {
+               set fcols [split $p "\t"]
+               set cols  [split [lindex $fcols 0] " "]
+               set stage [lindex $cols 2]
+               
+               set merge_stages($stage) [lrange $cols 0 1]
+       }
+
+       if {[eof $fd]} {
+               close $fd
+               unset merge_stages_fd
+               eval $cont
+       }
+}
+
+proc merge_resolve_tool {} {
+       global current_diff_path
+
+       merge_load_stages $current_diff_path [list merge_resolve_tool2]
+}
+
+proc merge_resolve_tool2 {} {
+       global current_diff_path merge_stages
+
+       # Validate the stages
+       if {$merge_stages(2) eq {} ||
+           [lindex $merge_stages(2) 0] eq {120000} ||
+           [lindex $merge_stages(2) 0] eq {160000} ||
+           $merge_stages(3) eq {} ||
+           [lindex $merge_stages(3) 0] eq {120000} ||
+           [lindex $merge_stages(3) 0] eq {160000}
+       } {
+               error_popup [mc "Cannot resolve deletion or link conflicts using a tool"]
+               return
+       }
+
+       if {![file exists $current_diff_path]} {
+               error_popup [mc "Conflict file does not exist"]
+               return
+       }
+
+       # Determine the tool to use
+       set tool [get_config merge.tool]
+       if {$tool eq {}} { set tool meld }
+
+       set merge_tool_path [get_config "mergetool.$tool.path"]
+       if {$merge_tool_path eq {}} {
+               switch -- $tool {
+               emerge { set merge_tool_path "emacs" }
+               araxis { set merge_tool_path "compare" }
+               default { set merge_tool_path $tool }
+               }
+       }
+
+       # Make file names
+       set filebase [file rootname $current_diff_path]
+       set fileext  [file extension $current_diff_path]
+       set basename [lindex [file split $current_diff_path] end]
+
+       set MERGED   $current_diff_path
+       set BASE     "./$MERGED.BASE$fileext"
+       set LOCAL    "./$MERGED.LOCAL$fileext"
+       set REMOTE   "./$MERGED.REMOTE$fileext"
+       set BACKUP   "./$MERGED.BACKUP$fileext"
+
+       set base_stage $merge_stages(1)
+
+       # Build the command line
+       switch -- $tool {
+       kdiff3 {
+               if {$base_stage ne {}} {
+                       set cmdline [list "$merge_tool_path" --auto --L1 "$MERGED (Base)" \
+                               --L2 "$MERGED (Local)" --L3 "$MERGED (Remote)" -o "$MERGED" "$BASE" "$LOCAL" "$REMOTE"]
+               } else {
+                       set cmdline [list "$merge_tool_path" --auto --L1 "$MERGED (Local)" \
+                               --L2 "$MERGED (Remote)" -o "$MERGED" "$LOCAL" "$REMOTE"]
+               }
+       }
+       tkdiff {
+               if {$base_stage ne {}} {
+                       set cmdline [list "$merge_tool_path" -a "$BASE" -o "$MERGED" "$LOCAL" "$REMOTE"]
+               } else {
+                       set cmdline [list "$merge_tool_path" -o "$MERGED" "$LOCAL" "$REMOTE"]
+               }
+       }
+       meld {
+               set cmdline [list "$merge_tool_path" "$LOCAL" "$MERGED" "$REMOTE"]
+       }
+       gvimdiff {
+               set cmdline [list "$merge_tool_path" -f "$LOCAL" "$MERGED" "$REMOTE"]
+       }
+       xxdiff {
+               if {$base_stage ne {}} {
+                       set cmdline [list "$merge_tool_path" -X --show-merged-pane \
+                                           -R {Accel.SaveAsMerged: "Ctrl-S"} \
+                                           -R {Accel.Search: "Ctrl+F"} \
+                                           -R {Accel.SearchForward: "Ctrl-G"} \
+                                           --merged-file "$MERGED" "$LOCAL" "$BASE" "$REMOTE"]
+               } else {
+                       set cmdline [list "$merge_tool_path" -X --show-merged-pane \
+                                           -R {Accel.SaveAsMerged: "Ctrl-S"} \
+                                           -R {Accel.Search: "Ctrl+F"} \
+                                           -R {Accel.SearchForward: "Ctrl-G"} \
+                                           --merged-file "$MERGED" "$LOCAL" "$REMOTE"]
+               }
+       }
+       opendiff {
+               if {$base_stage ne {}} {
+                       set cmdline [list "$merge_tool_path" "$LOCAL" "$REMOTE" -ancestor "$BASE" -merge "$MERGED"]
+               } else {
+                       set cmdline [list "$merge_tool_path" "$LOCAL" "$REMOTE" -merge "$MERGED"]
+               }
+       }
+       ecmerge {
+               if {$base_stage ne {}} {
+                       set cmdline [list "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" --default --mode=merge3 --to="$MERGED"]
+               } else {
+                       set cmdline [list "$merge_tool_path" "$LOCAL" "$REMOTE" --default --mode=merge2 --to="$MERGED"]
+               }
+       }
+       emerge {
+               if {$base_stage ne {}} {
+                       set cmdline [list "$merge_tool_path" -f emerge-files-with-ancestor-command \
+                                       "$LOCAL" "$REMOTE" "$BASE" "$basename"]
+               } else {
+                       set cmdline [list "$merge_tool_path" -f emerge-files-command \
+                                       "$LOCAL" "$REMOTE" "$basename"]
+               }
+       }
+       winmerge {
+               if {$base_stage ne {}} {
+                       # This tool does not support 3-way merges.
+                       # Use the 'conflict file' resolution feature instead.
+                       set cmdline [list "$merge_tool_path" -e -ub "$MERGED"]
+               } else {
+                       set cmdline [list "$merge_tool_path" -e -ub -wl \
+                               -dl "Theirs File" -dr "Mine File" "$REMOTE" "$LOCAL" "$MERGED"]
+               }
+       }
+       araxis {
+               if {$base_stage ne {}} {
+                       set cmdline [list "$merge_tool_path" -wait -merge -3 -a1 \
+                               -title1:"'$MERGED (Base)'" -title2:"'$MERGED (Local)'" \
+                               -title3:"'$MERGED (Remote)'" \
+                               "$BASE" "$LOCAL" "$REMOTE" "$MERGED"]
+               } else {
+                       set cmdline [list "$merge_tool_path" -wait -2 \
+                                -title1:"'$MERGED (Local)'" -title2:"'$MERGED (Remote)'" \
+                                "$LOCAL" "$REMOTE" "$MERGED"]
+               }
+       }
+       p4merge {
+               set cmdline [list "$merge_tool_path" "$BASE" "$REMOTE" "$LOCAL" "$MERGED"]
+       }
+       vimdiff {
+               error_popup [mc "Not a GUI merge tool: '%s'" $tool]
+               return
+       }
+       default {
+               error_popup [mc "Unsupported merge tool '%s'" $tool]
+               return
+       }
+       }
+
+       merge_tool_start $cmdline $MERGED $BACKUP [list $BASE $LOCAL $REMOTE]
+}
+
+proc delete_temp_files {files} {
+       foreach fname $files {
+               file delete $fname
+       }
+}
+
+proc merge_tool_get_stages {target stages} {
+       global merge_stages
+
+       set i 1
+       foreach fname $stages {
+               if {$merge_stages($i) eq {}} {
+                       file delete $fname
+                       catch { close [open $fname w] }
+               } else {
+                       # A hack to support autocrlf properly
+                       git checkout-index -f --stage=$i -- $target
+                       file rename -force -- $target $fname
+               }
+               incr i
+       }
+}
+
+proc merge_tool_start {cmdline target backup stages} {
+       global merge_stages mtool_target mtool_tmpfiles mtool_fd mtool_mtime
+
+       if {[info exists mtool_fd]} {
+               if {[ask_popup [mc "Merge tool is already running, terminate it?"]] eq {yes}} {
+                       catch { kill_file_process $mtool_fd }
+                       catch { close $mtool_fd }
+                       unset mtool_fd
+
+                       set old_backup [lindex $mtool_tmpfiles end]
+                       file rename -force -- $old_backup $mtool_target
+                       delete_temp_files $mtool_tmpfiles
+               } else {
+                       return
+               }
+       }
+
+       # Save the original file
+       file rename -force -- $target $backup
+
+       # Get the blobs; it destroys $target
+       if {[catch {merge_tool_get_stages $target $stages} err]} {
+               file rename -force -- $backup $target
+               delete_temp_files $stages
+               error_popup [mc "Error retrieving versions:\n%s" $err]
+               return
+       }
+
+       # Restore the conflict file
+       file copy -force -- $backup $target
+
+       # Initialize global state
+       set mtool_target $target
+       set mtool_mtime [file mtime $target]
+       set mtool_tmpfiles $stages
+
+       lappend mtool_tmpfiles $backup
+
+       # Force redirection to avoid interpreting output on stderr
+       # as an error, and launch the tool
+       lappend cmdline {2>@1}
+
+       if {[catch { set mtool_fd [_open_stdout_stderr $cmdline] } err]} {
+               delete_temp_files $mtool_tmpfiles
+               error_popup [mc "Could not start the merge tool:\n\n%s" $err]
+               return
+       }
+
+       ui_status [mc "Running merge tool..."]
+
+       fconfigure $mtool_fd -blocking 0 -translation binary -encoding binary
+       fileevent $mtool_fd readable [list read_mtool_output $mtool_fd]
+}
+
+proc read_mtool_output {fd} {
+       global mtool_fd mtool_tmpfiles
+
+       read $fd
+       if {[eof $fd]} {
+               unset mtool_fd
+
+               fconfigure $fd -blocking 1
+               merge_tool_finish $fd
+       }
+}
+
+proc merge_tool_finish {fd} {
+       global mtool_tmpfiles mtool_target mtool_mtime
+
+       set backup [lindex $mtool_tmpfiles end]
+       set failed 0
+
+       # Check the return code
+       if {[catch {close $fd} err]} {
+               set failed 1
+               if {$err ne {child process exited abnormally}} {
+                       error_popup [strcat [mc "Merge tool failed."] "\n\n$err"]
+               }
+       }
+
+       # Check the modification time of the target file
+       if {!$failed && [file mtime $mtool_target] eq $mtool_mtime} {
+               if {[ask_popup [mc "File %s unchanged, still accept as resolved?" \
+                               [short_path $mtool_target]]] ne {yes}} {
+                       set failed 1
+               }
+       }
+
+       # Finish
+       if {$failed} {
+               file rename -force -- $backup $mtool_target
+               delete_temp_files $mtool_tmpfiles
+               ui_status [mc "Merge tool failed."]
+       } else {
+               if {[is_config_true merge.keepbackup]} {
+                       file rename -force -- $backup "$mtool_target.orig"
+               }
+
+               delete_temp_files $mtool_tmpfiles
+
+               merge_add_resolution $mtool_target
+       }
+}
index 5e1346e601faf90114e9c62f11144f812835e872..9b865f6a754aeb0933e1696ae1c9ffaee899e936 100644 (file)
@@ -119,12 +119,14 @@ proc do_options {} {
                {b merge.summary {mc "Summarize Merge Commits"}}
                {i-1..5 merge.verbosity {mc "Merge Verbosity"}}
                {b merge.diffstat {mc "Show Diffstat After Merge"}}
+               {t merge.tool {mc "Use Merge Tool"}}
 
                {b gui.trustmtime  {mc "Trust File Modification Timestamps"}}
                {b gui.pruneduringfetch {mc "Prune Tracking Branches During Fetch"}}
                {b gui.matchtrackingbranch {mc "Match Tracking Branches"}}
                {b gui.fastcopyblame {mc "Blame Copy Only On Changed Files"}}
                {i-20..200 gui.copyblamethreshold {mc "Minimum Letters To Blame Copy On"}}
+               {i-0..300 gui.blamehistoryctx {mc "Blame History Context Radius (days)"}}
                {i-1..99 gui.diffcontext {mc "Number of Diff Context Lines"}}
                {i-0..99 gui.commitmsgwidth {mc "Commit Message Text Width"}}
                {t gui.newbranchtemplate {mc "New Branch Name Template"}}
index 087c4ac733be4b788751d0bae5b7aad22ce0dd99..2eaa2ae7d6f692f6063ebbd211eaab30212c2eae 100644 (file)
@@ -418,10 +418,12 @@ proc stop_rev_list {view} {
 }
 
 proc reset_pending_select {selid} {
-    global pending_select mainheadid
+    global pending_select mainheadid selectheadid
 
     if {$selid ne {}} {
        set pending_select $selid
+    } elseif {$selectheadid ne {}} {
+       set pending_select $selectheadid
     } else {
        set pending_select $mainheadid
     }
@@ -1609,6 +1611,7 @@ proc getcommit {id} {
 proc readrefs {} {
     global tagids idtags headids idheads tagobjid
     global otherrefids idotherrefs mainhead mainheadid
+    global selecthead selectheadid
 
     foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
        catch {unset $v}
@@ -1655,6 +1658,12 @@ proc readrefs {} {
            set mainhead [string range $thehead 11 end]
        }
     }
+    set selectheadid {}
+    if {$selecthead ne {}} {
+       catch {
+           set selectheadid [exec git rev-parse --verify $selecthead]
+       }
+    }
 }
 
 # skip over fake commits
@@ -2205,6 +2214,8 @@ proc makewindow {} {
        -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}
 }
 
 # Windows sends all mouse wheel events to the current focused window, not
@@ -3012,6 +3023,27 @@ proc external_diff {} {
     }
 }
 
+proc external_blame {parent_idx} {
+    global flist_menu_file
+    global nullid nullid2
+    global parentlist selectedline currentid
+
+    if {$parent_idx > 0} {
+       set base_commit [lindex $parentlist $selectedline [expr {$parent_idx-1}]]
+    } else {
+       set base_commit $currentid
+    }
+
+    if {$base_commit eq {} || $base_commit eq $nullid || $base_commit eq $nullid2} {
+       error_popup [mc "No such commit"]
+       return
+    }
+
+    if {[catch {exec git gui blame $base_commit $flist_menu_file &} err]} {
+       error_popup [mc "git gui blame: command failed: $err"]
+    }
+}
+
 # delete $dir when we see eof on $f (presumably because the child has exited)
 proc delete_at_eof {f dir} {
     while {[gets $f line] >= 0} {}
@@ -9865,6 +9897,9 @@ if {![file isdirectory $gitdir]} {
     exit 1
 }
 
+set selecthead {}
+set selectheadid {}
+
 set revtreeargs {}
 set cmdline_files {}
 set i 0
@@ -9876,6 +9911,9 @@ foreach arg $argv {
            set cmdline_files [lrange $argv [expr {$i + 1}] end]
            break
        }
+       "--select-commit=*" {
+           set selecthead [string range $arg 16 end]
+       }
        "--argscmd=*" {
            set revtreeargscmd [string range $arg 10 end]
        }
@@ -9886,6 +9924,10 @@ foreach arg $argv {
     incr i
 }
 
+if {$selecthead eq "HEAD"} {
+    set selecthead {}
+}
+
 if {$i >= [llength $argv] && $revtreeargs ne {}} {
     # no -- on command line, but some arguments (other than --argscmd)
     if {[catch {