lib / diff.tclon commit git-gui: handle "deleted symlink" diff marker (4ed1a19)
   1# git-gui diff viewer
   2# Copyright (C) 2006, 2007 Shawn Pearce
   3
   4proc clear_diff {} {
   5        global ui_diff current_diff_path current_diff_header
   6        global ui_index ui_workdir
   7
   8        $ui_diff conf -state normal
   9        $ui_diff delete 0.0 end
  10        $ui_diff conf -state disabled
  11
  12        set current_diff_path {}
  13        set current_diff_header {}
  14
  15        $ui_index tag remove in_diff 0.0 end
  16        $ui_workdir tag remove in_diff 0.0 end
  17}
  18
  19proc reshow_diff {} {
  20        global file_states file_lists
  21        global current_diff_path current_diff_side
  22
  23        set p $current_diff_path
  24        if {$p eq {}} {
  25                # No diff is being shown.
  26        } elseif {$current_diff_side eq {}
  27                || [catch {set s $file_states($p)}]
  28                || [lsearch -sorted -exact $file_lists($current_diff_side) $p] == -1} {
  29                clear_diff
  30        } else {
  31                show_diff $p $current_diff_side
  32        }
  33}
  34
  35proc handle_empty_diff {} {
  36        global current_diff_path file_states file_lists
  37
  38        set path $current_diff_path
  39        set s $file_states($path)
  40        if {[lindex $s 0] ne {_M}} return
  41
  42        info_popup "No differences detected.
  43
  44[short_path $path] has no changes.
  45
  46The modification date of this file was updated by another application, but the content within the file was not changed.
  47
  48A rescan will be automatically started to find other files which may have the same state."
  49
  50        clear_diff
  51        display_file $path __
  52        rescan ui_ready 0
  53}
  54
  55proc show_diff {path w {lno {}}} {
  56        global file_states file_lists
  57        global is_3way_diff diff_active repo_config
  58        global ui_diff ui_index ui_workdir
  59        global current_diff_path current_diff_side current_diff_header
  60
  61        if {$diff_active || ![lock_index read]} return
  62
  63        clear_diff
  64        if {$lno == {}} {
  65                set lno [lsearch -sorted -exact $file_lists($w) $path]
  66                if {$lno >= 0} {
  67                        incr lno
  68                }
  69        }
  70        if {$lno >= 1} {
  71                $w tag add in_diff $lno.0 [expr {$lno + 1}].0
  72        }
  73
  74        set s $file_states($path)
  75        set m [lindex $s 0]
  76        set is_3way_diff 0
  77        set diff_active 1
  78        set current_diff_path $path
  79        set current_diff_side $w
  80        set current_diff_header {}
  81        ui_status "Loading diff of [escape_path $path]..."
  82
  83        # - Git won't give us the diff, there's nothing to compare to!
  84        #
  85        if {$m eq {_O}} {
  86                set max_sz [expr {128 * 1024}]
  87                if {[catch {
  88                                if {[file type $path] == {link}} {
  89                                        set content [file readlink $path]
  90                                        set sz [string length $content]
  91                                } else {
  92                                        set fd [open $path r]
  93                                        fconfigure $fd -eofchar {}
  94                                        set content [read $fd $max_sz]
  95                                        close $fd
  96                                        set sz [file size $path]
  97                                }
  98                        } err ]} {
  99                        set diff_active 0
 100                        unlock_index
 101                        ui_status "Unable to display [escape_path $path]"
 102                        error_popup "Error loading file:\n\n$err"
 103                        return
 104                }
 105                $ui_diff conf -state normal
 106                if {![catch {set type [exec file $path]}]} {
 107                        set n [string length $path]
 108                        if {[string equal -length $n $path $type]} {
 109                                set type [string range $type $n end]
 110                                regsub {^:?\s*} $type {} type
 111                        }
 112                        $ui_diff insert end "* $type\n" d_@
 113                }
 114                if {[string first "\0" $content] != -1} {
 115                        $ui_diff insert end \
 116                                "* Binary file (not showing content)." \
 117                                d_@
 118                } else {
 119                        if {$sz > $max_sz} {
 120                                $ui_diff insert end \
 121"* Untracked file is $sz bytes.
 122* Showing only first $max_sz bytes.
 123" d_@
 124                        }
 125                        $ui_diff insert end $content
 126                        if {$sz > $max_sz} {
 127                                $ui_diff insert end "
 128* Untracked file clipped here by [appname].
 129* To see the entire file, use an external editor.
 130" d_@
 131                        }
 132                }
 133                $ui_diff conf -state disabled
 134                set diff_active 0
 135                unlock_index
 136                ui_ready
 137                return
 138        }
 139
 140        set cmd [list]
 141        if {$w eq $ui_index} {
 142                lappend cmd diff-index
 143                lappend cmd --cached
 144        } elseif {$w eq $ui_workdir} {
 145                if {[string index $m 0] eq {U}} {
 146                        lappend cmd diff
 147                } else {
 148                        lappend cmd diff-files
 149                }
 150        }
 151
 152        lappend cmd -p
 153        lappend cmd --no-color
 154        if {$repo_config(gui.diffcontext) >= 0} {
 155                lappend cmd "-U$repo_config(gui.diffcontext)"
 156        }
 157        if {$w eq $ui_index} {
 158                lappend cmd [PARENT]
 159        }
 160        lappend cmd --
 161        lappend cmd $path
 162
 163        if {[catch {set fd [eval git_read --nice $cmd]} err]} {
 164                set diff_active 0
 165                unlock_index
 166                ui_status "Unable to display [escape_path $path]"
 167                error_popup "Error loading diff:\n\n$err"
 168                return
 169        }
 170
 171        fconfigure $fd \
 172                -blocking 0 \
 173                -encoding binary \
 174                -translation binary
 175        fileevent $fd readable [list read_diff $fd]
 176}
 177
 178proc read_diff {fd} {
 179        global ui_diff diff_active
 180        global is_3way_diff current_diff_header
 181
 182        $ui_diff conf -state normal
 183        while {[gets $fd line] >= 0} {
 184                # -- Cleanup uninteresting diff header lines.
 185                #
 186                if {   [string match {diff --git *}      $line]
 187                        || [string match {diff --cc *}       $line]
 188                        || [string match {diff --combined *} $line]
 189                        || [string match {--- *}             $line]
 190                        || [string match {+++ *}             $line]} {
 191                        append current_diff_header $line "\n"
 192                        continue
 193                }
 194                if {[string match {index *} $line]} continue
 195                if {$line eq {deleted file mode 120000}} {
 196                        set line "deleted symlink"
 197                }
 198
 199                # -- Automatically detect if this is a 3 way diff.
 200                #
 201                if {[string match {@@@ *} $line]} {set is_3way_diff 1}
 202
 203                if {[string match {mode *} $line]
 204                        || [string match {new file *} $line]
 205                        || [string match {deleted file *} $line]
 206                        || [string match {deleted symlink} $line]
 207                        || [string match {Binary files * and * differ} $line]
 208                        || $line eq {\ No newline at end of file}
 209                        || [regexp {^\* Unmerged path } $line]} {
 210                        set tags {}
 211                } elseif {$is_3way_diff} {
 212                        set op [string range $line 0 1]
 213                        switch -- $op {
 214                        {  } {set tags {}}
 215                        {@@} {set tags d_@}
 216                        { +} {set tags d_s+}
 217                        { -} {set tags d_s-}
 218                        {+ } {set tags d_+s}
 219                        {- } {set tags d_-s}
 220                        {--} {set tags d_--}
 221                        {++} {
 222                                if {[regexp {^\+\+([<>]{7} |={7})} $line _g op]} {
 223                                        set line [string replace $line 0 1 {  }]
 224                                        set tags d$op
 225                                } else {
 226                                        set tags d_++
 227                                }
 228                        }
 229                        default {
 230                                puts "error: Unhandled 3 way diff marker: {$op}"
 231                                set tags {}
 232                        }
 233                        }
 234                } else {
 235                        set op [string index $line 0]
 236                        switch -- $op {
 237                        { } {set tags {}}
 238                        {@} {set tags d_@}
 239                        {-} {set tags d_-}
 240                        {+} {
 241                                if {[regexp {^\+([<>]{7} |={7})} $line _g op]} {
 242                                        set line [string replace $line 0 0 { }]
 243                                        set tags d$op
 244                                } else {
 245                                        set tags d_+
 246                                }
 247                        }
 248                        default {
 249                                puts "error: Unhandled 2 way diff marker: {$op}"
 250                                set tags {}
 251                        }
 252                        }
 253                }
 254                $ui_diff insert end $line $tags
 255                if {[string index $line end] eq "\r"} {
 256                        $ui_diff tag add d_cr {end - 2c}
 257                }
 258                $ui_diff insert end "\n" $tags
 259        }
 260        $ui_diff conf -state disabled
 261
 262        if {[eof $fd]} {
 263                close $fd
 264                set diff_active 0
 265                unlock_index
 266                ui_ready
 267
 268                if {[$ui_diff index end] eq {2.0}} {
 269                        handle_empty_diff
 270                }
 271        }
 272}
 273
 274proc apply_hunk {x y} {
 275        global current_diff_path current_diff_header current_diff_side
 276        global ui_diff ui_index file_states
 277
 278        if {$current_diff_path eq {} || $current_diff_header eq {}} return
 279        if {![lock_index apply_hunk]} return
 280
 281        set apply_cmd {apply --cached --whitespace=nowarn}
 282        set mi [lindex $file_states($current_diff_path) 0]
 283        if {$current_diff_side eq $ui_index} {
 284                set mode unstage
 285                lappend apply_cmd --reverse
 286                if {[string index $mi 0] ne {M}} {
 287                        unlock_index
 288                        return
 289                }
 290        } else {
 291                set mode stage
 292                if {[string index $mi 1] ne {M}} {
 293                        unlock_index
 294                        return
 295                }
 296        }
 297
 298        set s_lno [lindex [split [$ui_diff index @$x,$y] .] 0]
 299        set s_lno [$ui_diff search -backwards -regexp ^@@ $s_lno.0 0.0]
 300        if {$s_lno eq {}} {
 301                unlock_index
 302                return
 303        }
 304
 305        set e_lno [$ui_diff search -forwards -regexp ^@@ "$s_lno + 1 lines" end]
 306        if {$e_lno eq {}} {
 307                set e_lno end
 308        }
 309
 310        if {[catch {
 311                set p [eval git_write $apply_cmd]
 312                fconfigure $p -translation binary -encoding binary
 313                puts -nonewline $p $current_diff_header
 314                puts -nonewline $p [$ui_diff get $s_lno $e_lno]
 315                close $p} err]} {
 316                error_popup "Failed to $mode selected hunk.\n\n$err"
 317                unlock_index
 318                return
 319        }
 320
 321        $ui_diff conf -state normal
 322        $ui_diff delete $s_lno $e_lno
 323        $ui_diff conf -state disabled
 324
 325        if {[$ui_diff get 1.0 end] eq "\n"} {
 326                set o _
 327        } else {
 328                set o ?
 329        }
 330
 331        if {$current_diff_side eq $ui_index} {
 332                set mi ${o}M
 333        } elseif {[string index $mi 0] eq {_}} {
 334                set mi M$o
 335        } else {
 336                set mi ?$o
 337        }
 338        unlock_index
 339        display_file $current_diff_path $mi
 340        if {$o eq {_}} {
 341                clear_diff
 342        }
 343}