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