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 } elseif {$w eq $ui_workdir} {
291 if {[string first {U} $m] >= 0} {
292 lappend cmd diff
293 } else {
294 lappend cmd diff-files
295 }
296 }
297 if {![is_config_false gui.textconv] && [git-version >= 1.6.1]} {
298 lappend cmd --textconv
299 }
300
301 if {[string match {160000 *} [lindex $s 2]]
302 || [string match {160000 *} [lindex $s 3]]} {
303 set is_submodule_diff 1
304
305 if {[git-version >= "1.6.6"]} {
306 lappend cmd --submodule
307 }
308 }
309
310 lappend cmd -p
311 lappend cmd --color
312 set cmd [concat $cmd $repo_config(gui.diffopts)]
313 if {$repo_config(gui.diffcontext) >= 1} {
314 lappend cmd "-U$repo_config(gui.diffcontext)"
315 }
316 if {$w eq $ui_index} {
317 lappend cmd [PARENT]
318 }
319 if {$add_opts ne {}} {
320 eval lappend cmd $add_opts
321 } else {
322 lappend cmd --
323 lappend cmd $path
324 }
325
326 if {$is_submodule_diff && [git-version < "1.6.6"]} {
327 if {$w eq $ui_index} {
328 set cmd [list submodule summary --cached -- $path]
329 } else {
330 set cmd [list submodule summary --files -- $path]
331 }
332 }
333
334 if {[catch {set fd [eval git_read --nice $cmd]} err]} {
335 set diff_active 0
336 unlock_index
337 ui_status [mc "Unable to display %s" [escape_path $path]]
338 error_popup [strcat [mc "Error loading diff:"] "\n\n$err"]
339 return
340 }
341
342 set ::current_diff_inheader 1
343 fconfigure $fd \
344 -blocking 0 \
345 -encoding [get_path_encoding $path] \
346 -translation lf
347 fileevent $fd readable [list read_diff $fd $conflict_size $cont_info]
348}
349
350proc parse_color_line {line} {
351 set start 0
352 set result ""
353 set markup [list]
354 set regexp {\033\[((?:\d+;)*\d+)?m}
355 set need_reset 0
356 while {[regexp -indices -start $start $regexp $line match code]} {
357 foreach {begin end} $match break
358 append result [string range $line $start [expr {$begin - 1}]]
359 set pos [string length $result]
360 set col [eval [linsert $code 0 string range $line]]
361 set start [incr end]
362 if {$col eq "0" || $col eq ""} {
363 if {!$need_reset} continue
364 set need_reset 0
365 } else {
366 set need_reset 1
367 }
368 lappend markup $pos $col
369 }
370 append result [string range $line $start end]
371 if {[llength $markup] < 4} {set markup {}}
372 return [list $result $markup]
373}
374
375proc read_diff {fd conflict_size cont_info} {
376 global ui_diff diff_active is_submodule_diff
377 global is_3way_diff is_conflict_diff current_diff_header
378 global current_diff_queue
379 global diff_empty_count
380
381 $ui_diff conf -state normal
382 while {[gets $fd line] >= 0} {
383 foreach {line markup} [parse_color_line $line] break
384 set line [string map {\033 ^} $line]
385
386 set tags {}
387
388 # -- Check for start of diff header.
389 if { [string match {diff --git *} $line]
390 || [string match {diff --cc *} $line]
391 || [string match {diff --combined *} $line]} {
392 set ::current_diff_inheader 1
393 }
394
395 # -- Check for end of diff header (any hunk line will do this).
396 #
397 if {[regexp {^@@+ } $line]} {set ::current_diff_inheader 0}
398
399 # -- Automatically detect if this is a 3 way diff.
400 #
401 if {[string match {@@@ *} $line]} {set is_3way_diff 1}
402
403 if {$::current_diff_inheader} {
404
405 # -- These two lines stop a diff header and shouldn't be in there
406 if { [string match {Binary files * and * differ} $line]
407 || [regexp {^\* Unmerged path } $line]} {
408 set ::current_diff_inheader 0
409 } else {
410 append current_diff_header $line "\n"
411 }
412
413 # -- Cleanup uninteresting diff header lines.
414 #
415 if { [string match {diff --git *} $line]
416 || [string match {diff --cc *} $line]
417 || [string match {diff --combined *} $line]
418 || [string match {--- *} $line]
419 || [string match {+++ *} $line]
420 || [string match {index *} $line]} {
421 continue
422 }
423
424 # -- Name it symlink, not 120000
425 # Note, that the original line is in $current_diff_header
426 regsub {^(deleted|new) file mode 120000} $line {\1 symlink} line
427
428 } elseif { $line eq {\ No newline at end of file}} {
429 # -- Handle some special lines
430 } elseif {$is_3way_diff} {
431 set op [string range $line 0 1]
432 switch -- $op {
433 { } {set tags {}}
434 {@@} {set tags d_@}
435 { +} {set tags d_s+}
436 { -} {set tags d_s-}
437 {+ } {set tags d_+s}
438 {- } {set tags d_-s}
439 {--} {set tags d_--}
440 {++} {
441 set regexp [string map [list %conflict_size $conflict_size]\
442 {^\+\+([<>=]){%conflict_size}(?: |$)}]
443 if {[regexp $regexp $line _g op]} {
444 set is_conflict_diff 1
445 set line [string replace $line 0 1 { }]
446 set tags d$op
447 } else {
448 set tags d_++
449 }
450 }
451 default {
452 puts "error: Unhandled 3 way diff marker: {$op}"
453 set tags {}
454 }
455 }
456 } elseif {$is_submodule_diff} {
457 if {$line == ""} continue
458 if {[regexp {^Submodule } $line]} {
459 set tags d_info
460 } elseif {[regexp {^\* } $line]} {
461 set line [string replace $line 0 1 {Submodule }]
462 set tags d_info
463 } else {
464 set op [string range $line 0 2]
465 switch -- $op {
466 { <} {set tags d_-}
467 { >} {set tags d_+}
468 { W} {set tags {}}
469 default {
470 puts "error: Unhandled submodule diff marker: {$op}"
471 set tags {}
472 }
473 }
474 }
475 } else {
476 set op [string index $line 0]
477 switch -- $op {
478 { } {set tags {}}
479 {@} {set tags d_@}
480 {-} {set tags d_-}
481 {+} {
482 set regexp [string map [list %conflict_size $conflict_size]\
483 {^\+([<>=]){%conflict_size}(?: |$)}]
484 if {[regexp $regexp $line _g op]} {
485 set is_conflict_diff 1
486 set tags d$op
487 } else {
488 set tags d_+
489 }
490 }
491 default {
492 puts "error: Unhandled 2 way diff marker: {$op}"
493 set tags {}
494 }
495 }
496 }
497 set mark [$ui_diff index "end - 1 line linestart"]
498 $ui_diff insert end $line $tags
499 if {[string index $line end] eq "\r"} {
500 $ui_diff tag add d_cr {end - 2c}
501 }
502 $ui_diff insert end "\n" $tags
503
504 foreach {posbegin colbegin posend colend} $markup {
505 set prefix clr
506 foreach style [lsort -integer [split $colbegin ";"]] {
507 if {$style eq "7"} {append prefix i; continue}
508 if {$style != 4 && ($style < 30 || $style > 47)} {continue}
509 set a "$mark linestart + $posbegin chars"
510 set b "$mark linestart + $posend chars"
511 catch {$ui_diff tag add $prefix$style $a $b}
512 }
513 }
514 }
515 $ui_diff conf -state disabled
516
517 if {[eof $fd]} {
518 close $fd
519
520 if {$current_diff_queue ne {}} {
521 advance_diff_queue $cont_info
522 return
523 }
524
525 set diff_active 0
526 unlock_index
527 set scroll_pos [lindex $cont_info 0]
528 if {$scroll_pos ne {}} {
529 update
530 $ui_diff yview moveto $scroll_pos
531 }
532 ui_ready
533
534 if {[$ui_diff index end] eq {2.0}} {
535 handle_empty_diff
536 } else {
537 set diff_empty_count 0
538 }
539
540 set callback [lindex $cont_info 1]
541 if {$callback ne {}} {
542 eval $callback
543 }
544 }
545}
546
547proc apply_hunk {x y} {
548 global current_diff_path current_diff_header current_diff_side
549 global ui_diff ui_index file_states
550
551 if {$current_diff_path eq {} || $current_diff_header eq {}} return
552 if {![lock_index apply_hunk]} return
553
554 set apply_cmd {apply --cached --whitespace=nowarn}
555 set mi [lindex $file_states($current_diff_path) 0]
556 if {$current_diff_side eq $ui_index} {
557 set failed_msg [mc "Failed to unstage selected hunk."]
558 lappend apply_cmd --reverse
559 if {[string index $mi 0] ne {M}} {
560 unlock_index
561 return
562 }
563 } else {
564 set failed_msg [mc "Failed to stage selected hunk."]
565 if {[string index $mi 1] ne {M}} {
566 unlock_index
567 return
568 }
569 }
570
571 set s_lno [lindex [split [$ui_diff index @$x,$y] .] 0]
572 set s_lno [$ui_diff search -backwards -regexp ^@@ $s_lno.0 0.0]
573 if {$s_lno eq {}} {
574 unlock_index
575 return
576 }
577
578 set e_lno [$ui_diff search -forwards -regexp ^@@ "$s_lno + 1 lines" end]
579 if {$e_lno eq {}} {
580 set e_lno end
581 }
582
583 if {[catch {
584 set enc [get_path_encoding $current_diff_path]
585 set p [eval git_write $apply_cmd]
586 fconfigure $p -translation binary -encoding $enc
587 puts -nonewline $p $current_diff_header
588 puts -nonewline $p [$ui_diff get $s_lno $e_lno]
589 close $p} err]} {
590 error_popup [append $failed_msg "\n\n$err"]
591 unlock_index
592 return
593 }
594
595 $ui_diff conf -state normal
596 $ui_diff delete $s_lno $e_lno
597 $ui_diff conf -state disabled
598
599 if {[$ui_diff get 1.0 end] eq "\n"} {
600 set o _
601 } else {
602 set o ?
603 }
604
605 if {$current_diff_side eq $ui_index} {
606 set mi ${o}M
607 } elseif {[string index $mi 0] eq {_}} {
608 set mi M$o
609 } else {
610 set mi ?$o
611 }
612 unlock_index
613 display_file $current_diff_path $mi
614 # This should trigger shift to the next changed file
615 if {$o eq {_}} {
616 reshow_diff
617 }
618}
619
620proc apply_range_or_line {x y} {
621 global current_diff_path current_diff_header current_diff_side
622 global ui_diff ui_index file_states
623
624 set selected [$ui_diff tag nextrange sel 0.0]
625
626 if {$selected == {}} {
627 set first [$ui_diff index "@$x,$y"]
628 set last $first
629 } else {
630 set first [lindex $selected 0]
631 set last [lindex $selected 1]
632 }
633
634 set first_l [$ui_diff index "$first linestart"]
635 set last_l [$ui_diff index "$last lineend"]
636
637 if {$current_diff_path eq {} || $current_diff_header eq {}} return
638 if {![lock_index apply_hunk]} return
639
640 set apply_cmd {apply --cached --whitespace=nowarn}
641 set mi [lindex $file_states($current_diff_path) 0]
642 if {$current_diff_side eq $ui_index} {
643 set failed_msg [mc "Failed to unstage selected line."]
644 set to_context {+}
645 lappend apply_cmd --reverse
646 if {[string index $mi 0] ne {M}} {
647 unlock_index
648 return
649 }
650 } else {
651 set failed_msg [mc "Failed to stage selected line."]
652 set to_context {-}
653 if {[string index $mi 1] ne {M}} {
654 unlock_index
655 return
656 }
657 }
658
659 set wholepatch {}
660
661 while {$first_l < $last_l} {
662 set i_l [$ui_diff search -backwards -regexp ^@@ $first_l 0.0]
663 if {$i_l eq {}} {
664 # If there's not a @@ above, then the selected range
665 # must have come before the first_l @@
666 set i_l [$ui_diff search -regexp ^@@ $first_l $last_l]
667 }
668 if {$i_l eq {}} {
669 unlock_index
670 return
671 }
672 # $i_l is now at the beginning of a line
673
674 # pick start line number from hunk header
675 set hh [$ui_diff get $i_l "$i_l + 1 lines"]
676 set hh [lindex [split $hh ,] 0]
677 set hln [lindex [split $hh -] 1]
678
679 # There is a special situation to take care of. Consider this
680 # hunk:
681 #
682 # @@ -10,4 +10,4 @@
683 # context before
684 # -old 1
685 # -old 2
686 # +new 1
687 # +new 2
688 # context after
689 #
690 # We used to keep the context lines in the order they appear in
691 # the hunk. But then it is not possible to correctly stage only
692 # "-old 1" and "+new 1" - it would result in this staged text:
693 #
694 # context before
695 # old 2
696 # new 1
697 # context after
698 #
699 # (By symmetry it is not possible to *un*stage "old 2" and "new
700 # 2".)
701 #
702 # We resolve the problem by introducing an asymmetry, namely,
703 # when a "+" line is *staged*, it is moved in front of the
704 # context lines that are generated from the "-" lines that are
705 # immediately before the "+" block. That is, we construct this
706 # patch:
707 #
708 # @@ -10,4 +10,5 @@
709 # context before
710 # +new 1
711 # old 1
712 # old 2
713 # context after
714 #
715 # But we do *not* treat "-" lines that are *un*staged in a
716 # special way.
717 #
718 # With this asymmetry it is possible to stage the change "old
719 # 1" -> "new 1" directly, and to stage the change "old 2" ->
720 # "new 2" by first staging the entire hunk and then unstaging
721 # the change "old 1" -> "new 1".
722 #
723 # Applying multiple lines adds complexity to the special
724 # situation. The pre_context must be moved after the entire
725 # first block of consecutive staged "+" lines, so that
726 # staging both additions gives the following patch:
727 #
728 # @@ -10,4 +10,6 @@
729 # context before
730 # +new 1
731 # +new 2
732 # old 1
733 # old 2
734 # context after
735
736 # This is non-empty if and only if we are _staging_ changes;
737 # then it accumulates the consecutive "-" lines (after
738 # converting them to context lines) in order to be moved after
739 # "+" change lines.
740 set pre_context {}
741
742 set n 0
743 set m 0
744 set i_l [$ui_diff index "$i_l + 1 lines"]
745 set patch {}
746 while {[$ui_diff compare $i_l < "end - 1 chars"] &&
747 [$ui_diff get $i_l "$i_l + 2 chars"] ne {@@}} {
748 set next_l [$ui_diff index "$i_l + 1 lines"]
749 set c1 [$ui_diff get $i_l]
750 if {[$ui_diff compare $first_l <= $i_l] &&
751 [$ui_diff compare $i_l < $last_l] &&
752 ($c1 eq {-} || $c1 eq {+})} {
753 # a line to stage/unstage
754 set ln [$ui_diff get $i_l $next_l]
755 if {$c1 eq {-}} {
756 set n [expr $n+1]
757 set patch "$patch$pre_context$ln"
758 set pre_context {}
759 } else {
760 set m [expr $m+1]
761 set patch "$patch$ln"
762 }
763 } elseif {$c1 ne {-} && $c1 ne {+}} {
764 # context line
765 set ln [$ui_diff get $i_l $next_l]
766 set patch "$patch$pre_context$ln"
767 # Skip the "\ No newline at end of
768 # file". Depending on the locale setting
769 # we don't know what this line looks
770 # like exactly. The only thing we do
771 # know is that it starts with "\ "
772 if {![string match {\\ *} $ln]} {
773 set n [expr $n+1]
774 set m [expr $m+1]
775 }
776 set pre_context {}
777 } elseif {$c1 eq $to_context} {
778 # turn change line into context line
779 set ln [$ui_diff get "$i_l + 1 chars" $next_l]
780 if {$c1 eq {-}} {
781 set pre_context "$pre_context $ln"
782 } else {
783 set patch "$patch $ln"
784 }
785 set n [expr $n+1]
786 set m [expr $m+1]
787 } else {
788 # a change in the opposite direction of
789 # to_context which is outside the range of
790 # lines to apply.
791 set patch "$patch$pre_context"
792 set pre_context {}
793 }
794 set i_l $next_l
795 }
796 set patch "$patch$pre_context"
797 set wholepatch "$wholepatch@@ -$hln,$n +$hln,$m @@\n$patch"
798 set first_l [$ui_diff index "$next_l + 1 lines"]
799 }
800
801 if {[catch {
802 set enc [get_path_encoding $current_diff_path]
803 set p [eval git_write $apply_cmd]
804 fconfigure $p -translation binary -encoding $enc
805 puts -nonewline $p $current_diff_header
806 puts -nonewline $p $wholepatch
807 close $p} err]} {
808 error_popup [append $failed_msg "\n\n$err"]
809 }
810
811 unlock_index
812}