]> asedeno.scripts.mit.edu Git - git.git/blob - lib/diff.tcl
git-gui: handle "deleted symlink" diff marker
[git.git] / lib / diff.tcl
1 # git-gui diff viewer
2 # Copyright (C) 2006, 2007 Shawn Pearce
3
4 proc 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
19 proc 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
35 proc 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
46 The modification date of this file was updated by another application, but the content within the file was not changed.
47
48 A 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
55 proc 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
178 proc 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
274 proc 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 }