git-gui: Implement "Stage/Unstage Line"
[gitweb.git] / lib / diff.tcl
index d0134096409c78221fe5dd46e9af714e555965f5..96ba94906cf8037b58c2ba135654da840f9fff9a 100644 (file)
@@ -78,7 +78,7 @@ proc show_diff {path w {lno {}}} {
        set current_diff_path $path
        set current_diff_side $w
        set current_diff_header {}
-       ui_status "Loading diff of [escape_path $path]..."
+       ui_status [mc "Loading diff of %s..." [escape_path $path]]
 
        # - Git won't give us the diff, there's nothing to compare to!
        #
@@ -111,8 +111,8 @@ proc show_diff {path w {lno {}}} {
                        } err ]} {
                        set diff_active 0
                        unlock_index
-                       ui_status "Unable to display [escape_path $path]"
-                   error_popup [append [mc "Error loading file:"] "\n\n$err"]
+                       ui_status [mc "Unable to display %s" [escape_path $path]]
+                       error_popup [strcat [mc "Error loading file:"] "\n\n$err"]
                        return
                }
                $ui_diff conf -state normal
@@ -131,7 +131,7 @@ proc show_diff {path w {lno {}}} {
                }
                if {[string first "\0" $content] != -1} {
                        $ui_diff insert end \
-                               "* Binary file (not showing content)." \
+                               [mc "* Binary file (not showing content)."] \
                                d_@
                } else {
                        if {$sz > $max_sz} {
@@ -181,8 +181,8 @@ proc show_diff {path w {lno {}}} {
        if {[catch {set fd [eval git_read --nice $cmd]} err]} {
                set diff_active 0
                unlock_index
-               ui_status "Unable to display [escape_path $path]"
-               error_popup [append [mc "Error loading diff:"] "\n\n$err"]
+               ui_status [mc "Unable to display %s" [escape_path $path]]
+               error_popup [strcat [mc "Error loading diff:"] "\n\n$err"]
                return
        }
 
@@ -220,6 +220,7 @@ proc read_diff {fd} {
 
                if {[string match {mode *} $line]
                        || [string match {new file *} $line]
+                       || [regexp {^(old|new) mode *} $line]
                        || [string match {deleted file *} $line]
                        || [string match {deleted symlink} $line]
                        || [string match {Binary files * and * differ} $line]
@@ -357,5 +358,94 @@ proc apply_hunk {x y} {
        display_file $current_diff_path $mi
        if {$o eq {_}} {
                clear_diff
+       } else {
+               set current_diff_path $current_diff_path
+       }
+}
+
+proc apply_line {x y} {
+       global current_diff_path current_diff_header current_diff_side
+       global ui_diff ui_index file_states
+
+       if {$current_diff_path eq {} || $current_diff_header eq {}} return
+       if {![lock_index apply_hunk]} return
+
+       set apply_cmd {apply --cached --whitespace=nowarn}
+       set mi [lindex $file_states($current_diff_path) 0]
+       if {$current_diff_side eq $ui_index} {
+               set failed_msg [mc "Failed to unstage selected line."]
+               set to_context {+}
+               lappend apply_cmd --reverse
+               if {[string index $mi 0] ne {M}} {
+                       unlock_index
+                       return
+               }
+       } else {
+               set failed_msg [mc "Failed to stage selected line."]
+               set to_context {-}
+               if {[string index $mi 1] ne {M}} {
+                       unlock_index
+                       return
+               }
+       }
+
+       set the_l [$ui_diff index @$x,$y]
+
+       # operate only on change lines
+       set c1 [$ui_diff get "$the_l linestart"]
+       if {$c1 ne {+} && $c1 ne {-}} {
+               unlock_index
+               return
+       }
+       set sign $c1
+
+       set i_l [$ui_diff search -backwards -regexp ^@@ $the_l 0.0]
+       if {$i_l eq {}} {
+               unlock_index
+               return
+       }
+       # $i_l is now at the beginning of a line
+
+       # pick start line number from hunk header
+       set hh [$ui_diff get $i_l "$i_l + 1 lines"]
+       set hh [lindex [split $hh ,] 0]
+       set hln [lindex [split $hh -] 1]
+
+       set n 0
+       set i_l [$ui_diff index "$i_l + 1 lines"]
+       set patch {}
+       while {[$ui_diff compare $i_l < "end - 1 chars"] &&
+              [$ui_diff get $i_l "$i_l + 2 chars"] ne {@@}} {
+               set next_l [$ui_diff index "$i_l + 1 lines"]
+               set c1 [$ui_diff get $i_l]
+               if {[$ui_diff compare $i_l <= $the_l] &&
+                   [$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"
+               } elseif {$c1 ne {-} && $c1 ne {+}} {
+                       # context line
+                       set ln [$ui_diff get $i_l $next_l]
+                       set patch "$patch$ln"
+                       set n [expr $n+1]
+               } 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"
+                       set n [expr $n+1]
+               }
+               set i_l $next_l
        }
+       set patch "@@ -$hln,$n +$hln,[eval expr $n $sign 1] @@\n$patch"
+
+       if {[catch {
+               set p [eval git_write $apply_cmd]
+               fconfigure $p -translation binary -encoding binary
+               puts -nonewline $p $current_diff_header
+               puts -nonewline $p $patch
+               close $p} err]} {
+               error_popup [append $failed_msg "\n\n$err"]
+       }
+
+       unlock_index
 }