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