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}