9eeff2ed3590da610ce5eb2243f6d9d4474366c3
   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 {Binary files * and * differ} $line]
 207                        || $line eq {\ No newline at end of file}
 208                        || [regexp {^\* Unmerged path } $line]} {
 209                        set tags {}
 210                } elseif {$is_3way_diff} {
 211                        set op [string range $line 0 1]
 212                        switch -- $op {
 213                        {  } {set tags {}}
 214                        {@@} {set tags d_@}
 215                        { +} {set tags d_s+}
 216                        { -} {set tags d_s-}
 217                        {+ } {set tags d_+s}
 218                        {- } {set tags d_-s}
 219                        {--} {set tags d_--}
 220                        {++} {
 221                                if {[regexp {^\+\+([<>]{7} |={7})} $line _g op]} {
 222                                        set line [string replace $line 0 1 {  }]
 223                                        set tags d$op
 224                                } else {
 225                                        set tags d_++
 226                                }
 227                        }
 228                        default {
 229                                puts "error: Unhandled 3 way diff marker: {$op}"
 230                                set tags {}
 231                        }
 232                        }
 233                } else {
 234                        set op [string index $line 0]
 235                        switch -- $op {
 236                        { } {set tags {}}
 237                        {@} {set tags d_@}
 238                        {-} {set tags d_-}
 239                        {+} {
 240                                if {[regexp {^\+([<>]{7} |={7})} $line _g op]} {
 241                                        set line [string replace $line 0 0 { }]
 242                                        set tags d$op
 243                                } else {
 244                                        set tags d_+
 245                                }
 246                        }
 247                        default {
 248                                puts "error: Unhandled 2 way diff marker: {$op}"
 249                                set tags {}
 250                        }
 251                        }
 252                }
 253                $ui_diff insert end $line $tags
 254                if {[string index $line end] eq "\r"} {
 255                        $ui_diff tag add d_cr {end - 2c}
 256                }
 257                $ui_diff insert end "\n" $tags
 258        }
 259        $ui_diff conf -state disabled
 260
 261        if {[eof $fd]} {
 262                close $fd
 263                set diff_active 0
 264                unlock_index
 265                ui_ready
 266
 267                if {[$ui_diff index end] eq {2.0}} {
 268                        handle_empty_diff
 269                }
 270        }
 271}
 272
 273proc apply_hunk {x y} {
 274        global current_diff_path current_diff_header current_diff_side
 275        global ui_diff ui_index file_states
 276
 277        if {$current_diff_path eq {} || $current_diff_header eq {}} return
 278        if {![lock_index apply_hunk]} return
 279
 280        set apply_cmd {apply --cached --whitespace=nowarn}
 281        set mi [lindex $file_states($current_diff_path) 0]
 282        if {$current_diff_side eq $ui_index} {
 283                set mode unstage
 284                lappend apply_cmd --reverse
 285                if {[string index $mi 0] ne {M}} {
 286                        unlock_index
 287                        return
 288                }
 289        } else {
 290                set mode stage
 291                if {[string index $mi 1] ne {M}} {
 292                        unlock_index
 293                        return
 294                }
 295        }
 296
 297        set s_lno [lindex [split [$ui_diff index @$x,$y] .] 0]
 298        set s_lno [$ui_diff search -backwards -regexp ^@@ $s_lno.0 0.0]
 299        if {$s_lno eq {}} {
 300                unlock_index
 301                return
 302        }
 303
 304        set e_lno [$ui_diff search -forwards -regexp ^@@ "$s_lno + 1 lines" end]
 305        if {$e_lno eq {}} {
 306                set e_lno end
 307        }
 308
 309        if {[catch {
 310                set p [eval git_write $apply_cmd]
 311                fconfigure $p -translation binary -encoding binary
 312                puts -nonewline $p $current_diff_header
 313                puts -nonewline $p [$ui_diff get $s_lno $e_lno]
 314                close $p} err]} {
 315                error_popup "Failed to $mode selected hunk.\n\n$err"
 316                unlock_index
 317                return
 318        }
 319
 320        $ui_diff conf -state normal
 321        $ui_diff delete $s_lno $e_lno
 322        $ui_diff conf -state disabled
 323
 324        if {[$ui_diff get 1.0 end] eq "\n"} {
 325                set o _
 326        } else {
 327                set o ?
 328        }
 329
 330        if {$current_diff_side eq $ui_index} {
 331                set mi ${o}M
 332        } elseif {[string index $mi 0] eq {_}} {
 333                set mi M$o
 334        } else {
 335                set mi ?$o
 336        }
 337        unlock_index
 338        display_file $current_diff_path $mi
 339        if {$o eq {_}} {
 340                clear_diff
 341        }
 342}