Merge git://repo.or.cz/git-gui
authorJunio C Hamano <gitster@pobox.com>
Sun, 27 Jul 2008 21:09:25 +0000 (14:09 -0700)
committerJunio C Hamano <gitster@pobox.com>
Sun, 27 Jul 2008 21:09:25 +0000 (14:09 -0700)
* git://repo.or.cz/git-gui:
  git-gui: "Stage Line": Treat independent changes in adjacent lines better
  git-gui: Fix "Stage/Unstage Line" with one line of context.
  git-gui: Correct 'Visualize Branches' on Mac OS X to start gitk
  git-gui: Look for gitk in $PATH, not $LIBEXEC/git-core
  Add a menu item to invoke full copy detection in blame.
  Kill the blame back-end on window close.
  Add options to control the search for copies in blame.
  Fix pre-commit hooks under MinGW/MSYS

1  2 
git-gui/git-gui.sh
git-gui/lib/blame.tcl
git-gui/lib/diff.tcl
git-gui/lib/option.tcl
git-gui/macosx/AppMain.tcl

diff --combined git-gui/git-gui.sh
index 940677cbd8558574fd9f9fb5812a0bbf46198588,7c27a43a5de5394d2d1a482c456c1123798b64fc..7c27a43a5de5394d2d1a482c456c1123798b64fc
@@@ -473,10 -473,10 +473,10 @@@ proc githook_read {hook_name args} 
        set pchook [gitdir hooks $hook_name]
        lappend args 2>@1
  
-       # On Cygwin [file executable] might lie so we need to ask
+       # On Windows [file executable] might lie so we need to ask
        # the shell if the hook is executable.  Yes that's annoying.
        #
-       if {[is_Cygwin]} {
+       if {[is_Windows]} {
                upvar #0 _sh interp
                if {![info exists interp]} {
                        set interp [_which sh]
        return {}
  }
  
+ proc kill_file_process {fd} {
+       set process [pid $fd]
+       catch {
+               if {[is_Windows]} {
+                       # Use a Cygwin-specific flag to allow killing
+                       # native Windows processes
+                       exec kill -f $process
+               } else {
+                       exec kill $process
+               }
+       }
+ }
  proc sq {value} {
        regsub -all ' $value "'\\''" value
        return "'$value'"
@@@ -642,6 -656,8 +656,8 @@@ set default_config(user.email) {
  set default_config(gui.matchtrackingbranch) false
  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.diffcontext) 5
  set default_config(gui.commitmsgwidth) 75
  set default_config(gui.newbranchtemplate) {}
@@@ -1670,10 -1686,10 +1686,10 @@@ proc do_gitk {revs} 
        # -- Always start gitk through whatever we were loaded with.  This
        #    lets us bypass using shell process on Windows systems.
        #
-       set exe [file join [file dirname $::_git] gitk]
+       set exe [_which gitk]
        set cmd [list [info nameofexecutable] $exe]
-       if {! [file exists $exe]} {
-               error_popup [mc "Unable to start gitk:\n\n%s does not exist" $exe]
+       if {$exe eq {}} {
+               error_popup [mc "Couldn't find gitk in PATH"]
        } else {
                global env
  
diff --combined git-gui/lib/blame.tcl
index 92fac1bad402a0d7772af0b8817217555b61b6c7,b6e42cbc8fe0a49c301335f78cc2941bd9d59870..b6e42cbc8fe0a49c301335f78cc2941bd9d59870
@@@ -33,13 -33,6 +33,6 @@@ variable group_colors 
        #ececec
  }
  
- # Switches for original location detection
- #
- variable original_options [list -C -C]
- if {[git-version >= 1.5.3]} {
-       lappend original_options -w ; # ignore indentation changes
- }
  # Current blame data; cleared/reset on each load
  #
  field commit               ; # input commit to blame
@@@ -263,6 -256,9 +256,9 @@@ constructor new {i_commit i_path} 
        $w.ctxm add command \
                -label [mc "Copy Commit"] \
                -command [cb _copycommit]
+       $w.ctxm add command \
+               -label [mc "Do Full Copy Detection"] \
+               -command [cb _fullcopyblame]
  
        foreach i $w_columns {
                for {set g 0} {$g < [llength $group_colors]} {incr g} {
        bind $w.file_pane <Configure> \
        "if {{$w.file_pane} eq {%W}} {[cb _resize %h]}"
  
+       wm protocol $top WM_DELETE_WINDOW "destroy $top"
+       bind $top <Destroy> [cb _kill]
        _load $this {}
  }
  
+ method _kill {} {
+       if {$current_fd ne {}} {
+               kill_file_process $current_fd
+               catch {close $current_fd}
+               set current_fd {}
+       }
+ }
  method _load {jump} {
        variable group_colors
  
        _hide_tooltip $this
  
        if {$total_lines != 0 || $current_fd ne {}} {
-               if {$current_fd ne {}} {
-                       catch {close $current_fd}
-                       set current_fd {}
-               }
+               _kill $this
  
                foreach i $w_columns {
                        $i conf -state normal
@@@ -511,7 -515,6 +515,6 @@@ method _exec_blame {cur_w cur_d option
  method _read_blame {fd cur_w cur_d} {
        upvar #0 $cur_d line_data
        variable group_colors
-       variable original_options
  
        if {$fd ne $current_fd} {
                catch {close $fd}
        if {[eof $fd]} {
                close $fd
                if {$cur_w eq $w_asim} {
+                       # Switches for original location detection
+                       set threshold [get_config gui.copyblamethreshold]
+                       set original_options [list "-C$threshold"]
+                       if {![is_config_true gui.fastcopyblame]} {
+                               # thorough copy search; insert before the threshold
+                               set original_options [linsert $original_options 0 -C]
+                       }
+                       if {[git-version >= 1.5.3]} {
+                               lappend original_options -w ; # ignore indentation changes
+                       }
                        _exec_blame $this $w_amov @amov_data \
                                $original_options \
                                [mc "Loading original location annotations..."]
        }
  } ifdeleted { catch {close $fd} }
  
+ method _find_commit_bound {data_list start_idx delta} {
+       upvar #0 $data_list line_data
+       set pos $start_idx
+       set limit       [expr {[llength $line_data] - 1}]
+       set base_commit [lindex $line_data $pos 0]
+       while {$pos > 0 && $pos < $limit} {
+               set new_pos [expr {$pos + $delta}]
+               if {[lindex $line_data $new_pos 0] ne $base_commit} {
+                       return $pos
+               }
+               set pos $new_pos
+       }
+       return $pos
+ }
+ method _fullcopyblame {} {
+       if {$current_fd ne {}} {
+               tk_messageBox \
+                       -icon error \
+                       -type ok \
+                       -title [mc "Busy"] \
+                       -message [mc "Annotation process is already running."]
+               return
+       }
+       # Switches for original location detection
+       set threshold [get_config gui.copyblamethreshold]
+       set original_options [list -C -C "-C$threshold"]
+       if {[git-version >= 1.5.3]} {
+               lappend original_options -w ; # ignore indentation changes
+       }
+       # Find the line range
+       set pos @$::cursorX,$::cursorY
+       set lno [lindex [split [$::cursorW index $pos] .] 0]
+       set min_amov_lno [_find_commit_bound $this @amov_data $lno -1]
+       set max_amov_lno [_find_commit_bound $this @amov_data $lno 1]
+       set min_asim_lno [_find_commit_bound $this @asim_data $lno -1]
+       set max_asim_lno [_find_commit_bound $this @asim_data $lno 1]
+       if {$min_asim_lno < $min_amov_lno} {
+               set min_amov_lno $min_asim_lno
+       }
+       if {$max_asim_lno > $max_amov_lno} {
+               set max_amov_lno $max_asim_lno
+       }
+       lappend original_options -L "$min_amov_lno,$max_amov_lno"
+       # Clear lines
+       for {set i $min_amov_lno} {$i <= $max_amov_lno} {incr i} {
+               lset amov_data $i [list ]
+       }
+       # Start the back-end process
+       _exec_blame $this $w_amov @amov_data \
+               $original_options \
+               [mc "Running thorough copy detection..."]
+ }
  method _click {cur_w pos} {
        set lno [lindex [split [$cur_w index $pos] .] 0]
        _showcommit $this $cur_w $lno
diff --combined git-gui/lib/diff.tcl
index 96ba94906cf8037b58c2ba135654da840f9fff9a,77990c537c9d068fdcdf62a83b8c132399dff749..77990c537c9d068fdcdf62a83b8c132399dff749
@@@ -411,6 -411,53 +411,53 @@@ proc apply_line {x y} 
        set hh [lindex [split $hh ,] 0]
        set hln [lindex [split $hh -] 1]
  
+       # There is a special situation to take care of. Consider this hunk:
+       #
+       #    @@ -10,4 +10,4 @@
+       #     context before
+       #    -old 1
+       #    -old 2
+       #    +new 1
+       #    +new 2
+       #     context after
+       #
+       # We used to keep the context lines in the order they appear in the
+       # hunk. But then it is not possible to correctly stage only
+       # "-old 1" and "+new 1" - it would result in this staged text:
+       #
+       #    context before
+       #    old 2
+       #    new 1
+       #    context after
+       #
+       # (By symmetry it is not possible to *un*stage "old 2" and "new 2".)
+       #
+       # We resolve the problem by introducing an asymmetry, namely, when
+       # a "+" line is *staged*, it is moved in front of the context lines
+       # that are generated from the "-" lines that are immediately before
+       # the "+" block. That is, we construct this patch:
+       #
+       #    @@ -10,4 +10,5 @@
+       #     context before
+       #    +new 1
+       #     old 1
+       #     old 2
+       #     context after
+       #
+       # But we do *not* treat "-" lines that are *un*staged in a special
+       # way.
+       #
+       # With this asymmetry it is possible to stage the change
+       # "old 1" -> "new 1" directly, and to stage the change
+       # "old 2" -> "new 2" by first staging the entire hunk and
+       # then unstaging the change "old 1" -> "new 1".
+       # This is non-empty if and only if we are _staging_ changes;
+       # then it accumulates the consecutive "-" lines (after converting
+       # them to context lines) in order to be moved after the "+" change
+       # line.
+       set pre_context {}
        set n 0
        set i_l [$ui_diff index "$i_l + 1 lines"]
        set patch {}
                    [$ui_diff compare $the_l < $next_l]} {
                        # the line to stage/unstage
                        set ln [$ui_diff get $i_l $next_l]
-                       set patch "$patch$ln"
+                       if {$c1 eq {-}} {
+                               set n [expr $n+1]
+                               set patch "$patch$pre_context$ln"
+                       } else {
+                               set patch "$patch$ln$pre_context"
+                       }
+                       set pre_context {}
                } elseif {$c1 ne {-} && $c1 ne {+}} {
                        # context line
                        set ln [$ui_diff get $i_l $next_l]
-                       set patch "$patch$ln"
+                       set patch "$patch$pre_context$ln"
                        set n [expr $n+1]
+                       set pre_context {}
                } elseif {$c1 eq $to_context} {
                        # turn change line into context line
                        set ln [$ui_diff get "$i_l + 1 chars" $next_l]
-                       set patch "$patch $ln"
+                       if {$c1 eq {-}} {
+                               set pre_context "$pre_context $ln"
+                       } else {
+                               set patch "$patch $ln"
+                       }
                        set n [expr $n+1]
                }
                set i_l $next_l
diff --combined git-gui/lib/option.tcl
index 9270512582034a6629c4ff15abb1f30889f76903,ffb3f00ff0a992254804cc047b5a63ce82aa5bd9..ffb3f00ff0a992254804cc047b5a63ce82aa5bd9
@@@ -123,6 -123,8 +123,8 @@@ proc do_options {} 
                {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..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 41ca08e2b7929c59806b3b07a18dbae0ebfae933,ddbe6334a258dae46b6c333d53590f3b920a9cab..ddbe6334a258dae46b6c333d53590f3b920a9cab
@@@ -7,7 -7,7 +7,7 @@@ if {[string first -psn [lindex $argv 0]
  }
  
  if {[file tail [lindex $argv 0]] eq {gitk}} {
-       set argv0 [file join $gitexecdir gitk]
+       set argv0 [lindex $argv 0]
        set AppMain_source $argv0
  } else {
        set argv0 [file join $gitexecdir [file tail [lindex $argv 0]]]