]> git.ipfire.org Git - thirdparty/git.git/blame - lib/diff.tcl
git-gui: use wordprocessor tab style to ensure tabs work as expected
[thirdparty/git.git] / lib / diff.tcl
CommitLineData
f522c9b5
SP
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
7cf4566f 19proc reshow_diff {{after {}}} {
699d5601 20 global file_states file_lists
f522c9b5 21 global current_diff_path current_diff_side
25b8fb1e 22 global ui_diff
f522c9b5
SP
23
24 set p $current_diff_path
25 if {$p eq {}} {
26 # No diff is being shown.
29853b90 27 } elseif {$current_diff_side eq {}} {
f522c9b5 28 clear_diff
29853b90
AG
29 } elseif {[catch {set s $file_states($p)}]
30 || [lsearch -sorted -exact $file_lists($current_diff_side) $p] == -1} {
31
32 if {[find_next_diff $current_diff_side $p {} {[^O]}]} {
7cf4566f 33 next_diff $after
29853b90
AG
34 } else {
35 clear_diff
36 }
f522c9b5 37 } else {
25b8fb1e 38 set save_pos [lindex [$ui_diff yview] 0]
7cf4566f 39 show_diff $p $current_diff_side {} $save_pos $after
f522c9b5
SP
40 }
41}
42
3fe01623
AG
43proc force_diff_encoding {enc} {
44 global current_diff_path
45
46 if {$current_diff_path ne {}} {
47 force_path_encoding $current_diff_path $enc
48 reshow_diff
49 }
50}
51
f522c9b5
SP
52proc handle_empty_diff {} {
53 global current_diff_path file_states file_lists
584fa9cc 54 global diff_empty_count
f522c9b5
SP
55
56 set path $current_diff_path
57 set s $file_states($path)
1fbaccad 58 if {[lindex $s 0] ne {_M} || [has_textconv $path]} return
f522c9b5 59
584fa9cc
AG
60 # Prevent infinite rescan loops
61 incr diff_empty_count
62 if {$diff_empty_count > 1} return
63
1ac17950 64 info_popup [mc "No differences detected.
f522c9b5 65
1ac17950 66%s has no changes.
f522c9b5
SP
67
68The modification date of this file was updated by another application, but the content within the file was not changed.
69
1ac17950 70A rescan will be automatically started to find other files which may have the same state." [short_path $path]]
f522c9b5
SP
71
72 clear_diff
73 display_file $path __
699d5601 74 rescan ui_ready 0
f522c9b5
SP
75}
76
3e34838c 77proc show_diff {path w {lno {}} {scroll_pos {}} {callback {}}} {
f522c9b5 78 global file_states file_lists
3e34838c 79 global is_3way_diff is_conflict_diff diff_active repo_config
699d5601 80 global ui_diff ui_index ui_workdir
f522c9b5 81 global current_diff_path current_diff_side current_diff_header
b2ca4149 82 global current_diff_queue
f522c9b5
SP
83
84 if {$diff_active || ![lock_index read]} return
85
86 clear_diff
87 if {$lno == {}} {
88 set lno [lsearch -sorted -exact $file_lists($w) $path]
89 if {$lno >= 0} {
90 incr lno
91 }
92 }
93 if {$lno >= 1} {
94 $w tag add in_diff $lno.0 [expr {$lno + 1}].0
29853b90 95 $w see $lno.0
f522c9b5
SP
96 }
97
98 set s $file_states($path)
99 set m [lindex $s 0]
3e34838c 100 set is_conflict_diff 0
f522c9b5
SP
101 set current_diff_path $path
102 set current_diff_side $w
b2ca4149 103 set current_diff_queue {}
c8c4854b 104 ui_status [mc "Loading diff of %s..." [escape_path $path]]
f522c9b5 105
3e34838c
AG
106 set cont_info [list $scroll_pos $callback]
107
b2ca4149 108 if {[string first {U} $m] >= 0} {
3e34838c 109 merge_load_stages $path [list show_unmerged_diff $cont_info]
b2ca4149 110 } elseif {$m eq {_O}} {
3e34838c 111 show_other_diff $path $w $m $cont_info
b2ca4149 112 } else {
3e34838c 113 start_show_diff $cont_info
b2ca4149
AG
114 }
115}
116
3e34838c 117proc show_unmerged_diff {cont_info} {
b2ca4149 118 global current_diff_path current_diff_side
3e34838c 119 global merge_stages ui_diff is_conflict_diff
b2ca4149
AG
120 global current_diff_queue
121
122 if {$merge_stages(2) eq {}} {
3e34838c 123 set is_conflict_diff 1
b2ca4149 124 lappend current_diff_queue \
7f15b002 125 [list [mc "LOCAL: deleted\nREMOTE:\n"] d======= \
b2ca4149
AG
126 [list ":1:$current_diff_path" ":3:$current_diff_path"]]
127 } elseif {$merge_stages(3) eq {}} {
3e34838c 128 set is_conflict_diff 1
b2ca4149 129 lappend current_diff_queue \
7f15b002 130 [list [mc "REMOTE: deleted\nLOCAL:\n"] d======= \
b2ca4149
AG
131 [list ":1:$current_diff_path" ":2:$current_diff_path"]]
132 } elseif {[lindex $merge_stages(1) 0] eq {120000}
133 || [lindex $merge_stages(2) 0] eq {120000}
134 || [lindex $merge_stages(3) 0] eq {120000}} {
3e34838c 135 set is_conflict_diff 1
b2ca4149 136 lappend current_diff_queue \
7f15b002 137 [list [mc "LOCAL:\n"] d======= \
b2ca4149
AG
138 [list ":1:$current_diff_path" ":2:$current_diff_path"]]
139 lappend current_diff_queue \
7f15b002 140 [list [mc "REMOTE:\n"] d======= \
b2ca4149
AG
141 [list ":1:$current_diff_path" ":3:$current_diff_path"]]
142 } else {
3e34838c 143 start_show_diff $cont_info
b2ca4149
AG
144 return
145 }
146
3e34838c 147 advance_diff_queue $cont_info
b2ca4149
AG
148}
149
3e34838c 150proc advance_diff_queue {cont_info} {
b2ca4149
AG
151 global current_diff_queue ui_diff
152
153 set item [lindex $current_diff_queue 0]
154 set current_diff_queue [lrange $current_diff_queue 1 end]
155
156 $ui_diff conf -state normal
157 $ui_diff insert end [lindex $item 0] [lindex $item 1]
158 $ui_diff conf -state disabled
159
3e34838c 160 start_show_diff $cont_info [lindex $item 2]
b2ca4149
AG
161}
162
3e34838c 163proc show_other_diff {path w m cont_info} {
b2ca4149
AG
164 global file_states file_lists
165 global is_3way_diff diff_active repo_config
166 global ui_diff ui_index ui_workdir
167 global current_diff_path current_diff_side current_diff_header
168
f522c9b5
SP
169 # - Git won't give us the diff, there's nothing to compare to!
170 #
171 if {$m eq {_O}} {
f2df8a5b 172 set max_sz 100000
3b9dfde3 173 set type unknown
f522c9b5 174 if {[catch {
3b9dfde3
SP
175 set type [file type $path]
176 switch -- $type {
177 directory {
178 set type submodule
179 set content {}
180 set sz 0
181 }
182 link {
2d19f8e9
MB
183 set content [file readlink $path]
184 set sz [string length $content]
3b9dfde3
SP
185 }
186 file {
2d19f8e9 187 set fd [open $path r]
1ffca60f
SP
188 fconfigure $fd \
189 -eofchar {} \
72e6b002 190 -encoding [get_path_encoding $path]
2d19f8e9
MB
191 set content [read $fd $max_sz]
192 close $fd
193 set sz [file size $path]
194 }
3b9dfde3
SP
195 default {
196 error "'$type' not supported"
197 }
198 }
f522c9b5
SP
199 } err ]} {
200 set diff_active 0
201 unlock_index
c8c4854b 202 ui_status [mc "Unable to display %s" [escape_path $path]]
31bb1d1b 203 error_popup [strcat [mc "Error loading file:"] "\n\n$err"]
f522c9b5
SP
204 return
205 }
206 $ui_diff conf -state normal
3b9dfde3 207 if {$type eq {submodule}} {
5f51ccd2
SP
208 $ui_diff insert end [append \
209 "* " \
210 [mc "Git Repository (subproject)"] \
211 "\n"] d_@
3b9dfde3 212 } elseif {![catch {set type [exec file $path]}]} {
f522c9b5
SP
213 set n [string length $path]
214 if {[string equal -length $n $path $type]} {
215 set type [string range $type $n end]
216 regsub {^:?\s*} $type {} type
217 }
218 $ui_diff insert end "* $type\n" d_@
219 }
220 if {[string first "\0" $content] != -1} {
221 $ui_diff insert end \
c8c4854b 222 [mc "* Binary file (not showing content)."] \
f522c9b5
SP
223 d_@
224 } else {
225 if {$sz > $max_sz} {
7f15b002
JS
226 $ui_diff insert end [mc \
227"* Untracked file is %d bytes.
228* Showing only first %d bytes.
229" $sz $max_sz] d_@
f522c9b5
SP
230 }
231 $ui_diff insert end $content
232 if {$sz > $max_sz} {
7f15b002
JS
233 $ui_diff insert end [mc "
234* Untracked file clipped here by %s.
f522c9b5 235* To see the entire file, use an external editor.
7f15b002 236" [appname]] d_@
f522c9b5
SP
237 }
238 }
239 $ui_diff conf -state disabled
240 set diff_active 0
241 unlock_index
3e34838c 242 set scroll_pos [lindex $cont_info 0]
25b8fb1e
AG
243 if {$scroll_pos ne {}} {
244 update
245 $ui_diff yview moveto $scroll_pos
246 }
699d5601 247 ui_ready
3e34838c
AG
248 set callback [lindex $cont_info 1]
249 if {$callback ne {}} {
250 eval $callback
251 }
f522c9b5
SP
252 return
253 }
b2ca4149
AG
254}
255
3e34838c 256proc start_show_diff {cont_info {add_opts {}}} {
b2ca4149 257 global file_states file_lists
246295bd 258 global is_3way_diff is_submodule_diff diff_active repo_config
b2ca4149
AG
259 global ui_diff ui_index ui_workdir
260 global current_diff_path current_diff_side current_diff_header
261
262 set path $current_diff_path
263 set w $current_diff_side
264
265 set s $file_states($path)
266 set m [lindex $s 0]
267 set is_3way_diff 0
246295bd 268 set is_submodule_diff 0
b2ca4149
AG
269 set diff_active 1
270 set current_diff_header {}
f522c9b5 271
0b812616 272 set cmd [list]
f522c9b5
SP
273 if {$w eq $ui_index} {
274 lappend cmd diff-index
275 lappend cmd --cached
276 } elseif {$w eq $ui_workdir} {
ff515d81 277 if {[string first {U} $m] >= 0} {
f522c9b5
SP
278 lappend cmd diff
279 } else {
280 lappend cmd diff-files
281 }
282 }
1fbaccad
CP
283 if {![is_config_false gui.textconv] && [git-version >= 1.6.1]} {
284 lappend cmd --textconv
285 }
f522c9b5 286
a9ae14a1
JL
287 if {[string match {160000 *} [lindex $s 2]]
288 || [string match {160000 *} [lindex $s 3]]} {
289 set is_submodule_diff 1
290
291 if {[git-version >= "1.6.6"]} {
292 lappend cmd --submodule
293 }
294 }
295
f522c9b5
SP
296 lappend cmd -p
297 lappend cmd --no-color
55ba8a34 298 if {$repo_config(gui.diffcontext) >= 1} {
f522c9b5
SP
299 lappend cmd "-U$repo_config(gui.diffcontext)"
300 }
301 if {$w eq $ui_index} {
302 lappend cmd [PARENT]
303 }
b2ca4149
AG
304 if {$add_opts ne {}} {
305 eval lappend cmd $add_opts
306 } else {
307 lappend cmd --
308 lappend cmd $path
309 }
f522c9b5 310
a9ae14a1 311 if {$is_submodule_diff && [git-version < "1.6.6"]} {
af413de4 312 if {$w eq $ui_index} {
118d9388 313 set cmd [list submodule summary --cached -- $path]
af413de4 314 } else {
118d9388 315 set cmd [list submodule summary --files -- $path]
af413de4 316 }
246295bd
JL
317 }
318
0b812616 319 if {[catch {set fd [eval git_read --nice $cmd]} err]} {
f522c9b5
SP
320 set diff_active 0
321 unlock_index
c8c4854b 322 ui_status [mc "Unable to display %s" [escape_path $path]]
31bb1d1b 323 error_popup [strcat [mc "Error loading diff:"] "\n\n$err"]
f522c9b5
SP
324 return
325 }
326
ca53c3fd 327 set ::current_diff_inheader 1
f522c9b5
SP
328 fconfigure $fd \
329 -blocking 0 \
72e6b002 330 -encoding [get_path_encoding $path] \
1ffca60f 331 -translation lf
3e34838c 332 fileevent $fd readable [list read_diff $fd $cont_info]
f522c9b5
SP
333}
334
3e34838c 335proc read_diff {fd cont_info} {
246295bd 336 global ui_diff diff_active is_submodule_diff
3e34838c 337 global is_3way_diff is_conflict_diff current_diff_header
b2ca4149 338 global current_diff_queue
584fa9cc 339 global diff_empty_count
f522c9b5
SP
340
341 $ui_diff conf -state normal
342 while {[gets $fd line] >= 0} {
343 # -- Cleanup uninteresting diff header lines.
344 #
ca53c3fd
SP
345 if {$::current_diff_inheader} {
346 if { [string match {diff --git *} $line]
347 || [string match {diff --cc *} $line]
348 || [string match {diff --combined *} $line]
349 || [string match {--- *} $line]
350 || [string match {+++ *} $line]} {
351 append current_diff_header $line "\n"
352 continue
353 }
f522c9b5
SP
354 }
355 if {[string match {index *} $line]} continue
356 if {$line eq {deleted file mode 120000}} {
357 set line "deleted symlink"
358 }
ca53c3fd 359 set ::current_diff_inheader 0
f522c9b5
SP
360
361 # -- Automatically detect if this is a 3 way diff.
362 #
363 if {[string match {@@@ *} $line]} {set is_3way_diff 1}
364
365 if {[string match {mode *} $line]
366 || [string match {new file *} $line]
a4750dd2 367 || [regexp {^(old|new) mode *} $line]
f522c9b5 368 || [string match {deleted file *} $line]
4ed1a190 369 || [string match {deleted symlink} $line]
f522c9b5
SP
370 || [string match {Binary files * and * differ} $line]
371 || $line eq {\ No newline at end of file}
372 || [regexp {^\* Unmerged path } $line]} {
373 set tags {}
374 } elseif {$is_3way_diff} {
375 set op [string range $line 0 1]
376 switch -- $op {
377 { } {set tags {}}
378 {@@} {set tags d_@}
379 { +} {set tags d_s+}
380 { -} {set tags d_s-}
381 {+ } {set tags d_+s}
382 {- } {set tags d_-s}
383 {--} {set tags d_--}
384 {++} {
385 if {[regexp {^\+\+([<>]{7} |={7})} $line _g op]} {
3e34838c 386 set is_conflict_diff 1
f522c9b5
SP
387 set line [string replace $line 0 1 { }]
388 set tags d$op
389 } else {
390 set tags d_++
391 }
392 }
393 default {
394 puts "error: Unhandled 3 way diff marker: {$op}"
395 set tags {}
396 }
397 }
246295bd
JL
398 } elseif {$is_submodule_diff} {
399 if {$line == ""} continue
a9ae14a1
JL
400 if {[regexp {^Submodule } $line]} {
401 set tags d_@
402 } elseif {[regexp {^\* } $line]} {
246295bd
JL
403 set line [string replace $line 0 1 {Submodule }]
404 set tags d_@
405 } else {
406 set op [string range $line 0 2]
407 switch -- $op {
408 { <} {set tags d_-}
409 { >} {set tags d_+}
410 { W} {set tags {}}
411 default {
412 puts "error: Unhandled submodule diff marker: {$op}"
413 set tags {}
414 }
415 }
416 }
f522c9b5
SP
417 } else {
418 set op [string index $line 0]
419 switch -- $op {
420 { } {set tags {}}
421 {@} {set tags d_@}
422 {-} {set tags d_-}
423 {+} {
424 if {[regexp {^\+([<>]{7} |={7})} $line _g op]} {
3e34838c 425 set is_conflict_diff 1
f522c9b5
SP
426 set tags d$op
427 } else {
428 set tags d_+
429 }
430 }
431 default {
432 puts "error: Unhandled 2 way diff marker: {$op}"
433 set tags {}
434 }
435 }
436 }
437 $ui_diff insert end $line $tags
438 if {[string index $line end] eq "\r"} {
439 $ui_diff tag add d_cr {end - 2c}
440 }
441 $ui_diff insert end "\n" $tags
442 }
443 $ui_diff conf -state disabled
444
445 if {[eof $fd]} {
446 close $fd
b2ca4149
AG
447
448 if {$current_diff_queue ne {}} {
3e34838c 449 advance_diff_queue $cont_info
b2ca4149
AG
450 return
451 }
452
f522c9b5
SP
453 set diff_active 0
454 unlock_index
3e34838c 455 set scroll_pos [lindex $cont_info 0]
25b8fb1e
AG
456 if {$scroll_pos ne {}} {
457 update
458 $ui_diff yview moveto $scroll_pos
459 }
699d5601 460 ui_ready
f522c9b5
SP
461
462 if {[$ui_diff index end] eq {2.0}} {
463 handle_empty_diff
584fa9cc
AG
464 } else {
465 set diff_empty_count 0
f522c9b5 466 }
584fa9cc 467
3e34838c
AG
468 set callback [lindex $cont_info 1]
469 if {$callback ne {}} {
470 eval $callback
471 }
f522c9b5
SP
472 }
473}
474
475proc apply_hunk {x y} {
476 global current_diff_path current_diff_header current_diff_side
477 global ui_diff ui_index file_states
478
479 if {$current_diff_path eq {} || $current_diff_header eq {}} return
480 if {![lock_index apply_hunk]} return
481
0b812616 482 set apply_cmd {apply --cached --whitespace=nowarn}
f522c9b5
SP
483 set mi [lindex $file_states($current_diff_path) 0]
484 if {$current_diff_side eq $ui_index} {
1ac17950 485 set failed_msg [mc "Failed to unstage selected hunk."]
f522c9b5
SP
486 lappend apply_cmd --reverse
487 if {[string index $mi 0] ne {M}} {
488 unlock_index
489 return
490 }
491 } else {
1ac17950 492 set failed_msg [mc "Failed to stage selected hunk."]
f522c9b5
SP
493 if {[string index $mi 1] ne {M}} {
494 unlock_index
495 return
496 }
497 }
498
499 set s_lno [lindex [split [$ui_diff index @$x,$y] .] 0]
500 set s_lno [$ui_diff search -backwards -regexp ^@@ $s_lno.0 0.0]
501 if {$s_lno eq {}} {
502 unlock_index
503 return
504 }
505
506 set e_lno [$ui_diff search -forwards -regexp ^@@ "$s_lno + 1 lines" end]
507 if {$e_lno eq {}} {
508 set e_lno end
509 }
510
511 if {[catch {
72e6b002 512 set enc [get_path_encoding $current_diff_path]
0b812616 513 set p [eval git_write $apply_cmd]
72e6b002 514 fconfigure $p -translation binary -encoding $enc
f522c9b5
SP
515 puts -nonewline $p $current_diff_header
516 puts -nonewline $p [$ui_diff get $s_lno $e_lno]
517 close $p} err]} {
1ac17950 518 error_popup [append $failed_msg "\n\n$err"]
f522c9b5
SP
519 unlock_index
520 return
521 }
522
523 $ui_diff conf -state normal
524 $ui_diff delete $s_lno $e_lno
525 $ui_diff conf -state disabled
526
527 if {[$ui_diff get 1.0 end] eq "\n"} {
528 set o _
529 } else {
530 set o ?
531 }
532
533 if {$current_diff_side eq $ui_index} {
534 set mi ${o}M
535 } elseif {[string index $mi 0] eq {_}} {
536 set mi M$o
537 } else {
538 set mi ?$o
539 }
540 unlock_index
541 display_file $current_diff_path $mi
29853b90 542 # This should trigger shift to the next changed file
f522c9b5 543 if {$o eq {_}} {
29853b90 544 reshow_diff
f522c9b5
SP
545 }
546}
5821988f 547
ff07c3b6 548proc apply_range_or_line {x y} {
5821988f
JS
549 global current_diff_path current_diff_header current_diff_side
550 global ui_diff ui_index file_states
551
ff07c3b6
JE
552 set selected [$ui_diff tag nextrange sel 0.0]
553
554 if {$selected == {}} {
555 set first [$ui_diff index "@$x,$y"]
556 set last $first
557 } else {
558 set first [lindex $selected 0]
559 set last [lindex $selected 1]
560 }
561
562 set first_l [$ui_diff index "$first linestart"]
563 set last_l [$ui_diff index "$last lineend"]
564
5821988f
JS
565 if {$current_diff_path eq {} || $current_diff_header eq {}} return
566 if {![lock_index apply_hunk]} return
567
568 set apply_cmd {apply --cached --whitespace=nowarn}
569 set mi [lindex $file_states($current_diff_path) 0]
570 if {$current_diff_side eq $ui_index} {
571 set failed_msg [mc "Failed to unstage selected line."]
572 set to_context {+}
573 lappend apply_cmd --reverse
574 if {[string index $mi 0] ne {M}} {
575 unlock_index
576 return
577 }
578 } else {
579 set failed_msg [mc "Failed to stage selected line."]
580 set to_context {-}
581 if {[string index $mi 1] ne {M}} {
582 unlock_index
583 return
584 }
585 }
586
ff07c3b6 587 set wholepatch {}
5821988f 588
ff07c3b6
JE
589 while {$first_l < $last_l} {
590 set i_l [$ui_diff search -backwards -regexp ^@@ $first_l 0.0]
591 if {$i_l eq {}} {
592 # If there's not a @@ above, then the selected range
593 # must have come before the first_l @@
594 set i_l [$ui_diff search -regexp ^@@ $first_l $last_l]
595 }
596 if {$i_l eq {}} {
597 unlock_index
598 return
599 }
600 # $i_l is now at the beginning of a line
5821988f 601
ff07c3b6
JE
602 # pick start line number from hunk header
603 set hh [$ui_diff get $i_l "$i_l + 1 lines"]
604 set hh [lindex [split $hh ,] 0]
605 set hln [lindex [split $hh -] 1]
5821988f 606
ff07c3b6
JE
607 # There is a special situation to take care of. Consider this
608 # hunk:
609 #
610 # @@ -10,4 +10,4 @@
611 # context before
612 # -old 1
613 # -old 2
614 # +new 1
615 # +new 2
616 # context after
617 #
618 # We used to keep the context lines in the order they appear in
619 # the hunk. But then it is not possible to correctly stage only
620 # "-old 1" and "+new 1" - it would result in this staged text:
621 #
622 # context before
623 # old 2
624 # new 1
625 # context after
626 #
627 # (By symmetry it is not possible to *un*stage "old 2" and "new
628 # 2".)
629 #
630 # We resolve the problem by introducing an asymmetry, namely,
631 # when a "+" line is *staged*, it is moved in front of the
632 # context lines that are generated from the "-" lines that are
633 # immediately before the "+" block. That is, we construct this
634 # patch:
635 #
636 # @@ -10,4 +10,5 @@
637 # context before
638 # +new 1
639 # old 1
640 # old 2
641 # context after
642 #
643 # But we do *not* treat "-" lines that are *un*staged in a
644 # special way.
645 #
646 # With this asymmetry it is possible to stage the change "old
647 # 1" -> "new 1" directly, and to stage the change "old 2" ->
648 # "new 2" by first staging the entire hunk and then unstaging
649 # the change "old 1" -> "new 1".
650 #
651 # Applying multiple lines adds complexity to the special
652 # situation. The pre_context must be moved after the entire
653 # first block of consecutive staged "+" lines, so that
654 # staging both additions gives the following patch:
655 #
656 # @@ -10,4 +10,6 @@
657 # context before
658 # +new 1
659 # +new 2
660 # old 1
661 # old 2
662 # context after
663
664 # This is non-empty if and only if we are _staging_ changes;
665 # then it accumulates the consecutive "-" lines (after
666 # converting them to context lines) in order to be moved after
667 # "+" change lines.
668 set pre_context {}
669
670 set n 0
671 set m 0
672 set i_l [$ui_diff index "$i_l + 1 lines"]
673 set patch {}
674 while {[$ui_diff compare $i_l < "end - 1 chars"] &&
675 [$ui_diff get $i_l "$i_l + 2 chars"] ne {@@}} {
676 set next_l [$ui_diff index "$i_l + 1 lines"]
677 set c1 [$ui_diff get $i_l]
678 if {[$ui_diff compare $first_l <= $i_l] &&
679 [$ui_diff compare $i_l < $last_l] &&
680 ($c1 eq {-} || $c1 eq {+})} {
681 # a line to stage/unstage
682 set ln [$ui_diff get $i_l $next_l]
683 if {$c1 eq {-}} {
684 set n [expr $n+1]
685 set patch "$patch$pre_context$ln"
686 set pre_context {}
687 } else {
688 set m [expr $m+1]
689 set patch "$patch$ln"
690 }
691 } elseif {$c1 ne {-} && $c1 ne {+}} {
692 # context line
693 set ln [$ui_diff get $i_l $next_l]
c7f74570 694 set patch "$patch$pre_context$ln"
ff07c3b6
JE
695 set n [expr $n+1]
696 set m [expr $m+1]
697 set pre_context {}
698 } elseif {$c1 eq $to_context} {
699 # turn change line into context line
700 set ln [$ui_diff get "$i_l + 1 chars" $next_l]
701 if {$c1 eq {-}} {
702 set pre_context "$pre_context $ln"
703 } else {
704 set patch "$patch $ln"
705 }
706 set n [expr $n+1]
707 set m [expr $m+1]
c7f74570 708 } else {
ff07c3b6
JE
709 # a change in the opposite direction of
710 # to_context which is outside the range of
711 # lines to apply.
712 set patch "$patch$pre_context"
713 set pre_context {}
c7f74570 714 }
ff07c3b6 715 set i_l $next_l
5821988f 716 }
ff07c3b6
JE
717 set patch "$patch$pre_context"
718 set wholepatch "$wholepatch@@ -$hln,$n +$hln,$m @@\n$patch"
719 set first_l [$ui_diff index "$next_l + 1 lines"]
5821988f 720 }
5821988f
JS
721
722 if {[catch {
72e6b002 723 set enc [get_path_encoding $current_diff_path]
5821988f 724 set p [eval git_write $apply_cmd]
72e6b002 725 fconfigure $p -translation binary -encoding $enc
5821988f 726 puts -nonewline $p $current_diff_header
ff07c3b6 727 puts -nonewline $p $wholepatch
5821988f
JS
728 close $p} err]} {
729 error_popup [append $failed_msg "\n\n$err"]
730 }
731
732 unlock_index
733}