Remove the info popup, it is useless since it states that an automatic
[git-gui/kampbell.git] / lib / diff.tcl
blob6e2d342533dbc08422fcb621c4958389926b1a22
1 # git-gui diff viewer
2 # Copyright (C) 2006, 2007 Shawn Pearce
4 proc apply_tab_size {{firsttab {}}} {
5 global have_tk85 repo_config ui_diff
7 set w [font measure font_diff "0"]
8 if {$have_tk85 && $firsttab != 0} {
9 $ui_diff configure -tabs [list [expr {$firsttab * $w}] [expr {($firsttab + $repo_config(gui.tabsize)) * $w}]]
10 } elseif {$have_tk85 || $repo_config(gui.tabsize) != 8} {
11 $ui_diff configure -tabs [expr {$repo_config(gui.tabsize) * $w}]
12 } else {
13 $ui_diff configure -tabs {}
17 proc clear_diff {} {
18 global ui_diff current_diff_path current_diff_header
19 global ui_index ui_workdir
21 $ui_diff conf -state normal
22 $ui_diff delete 0.0 end
23 $ui_diff conf -state disabled
25 set current_diff_path {}
26 set current_diff_header {}
28 $ui_index tag remove in_diff 0.0 end
29 $ui_workdir tag remove in_diff 0.0 end
32 proc reshow_diff {{after {}}} {
33 global file_states file_lists
34 global current_diff_path current_diff_side
35 global ui_diff
37 set p $current_diff_path
38 if {$p eq {}} {
39 # No diff is being shown.
40 } elseif {$current_diff_side eq {}} {
41 clear_diff
42 } elseif {[catch {set s $file_states($p)}]
43 || [lsearch -sorted -exact $file_lists($current_diff_side) $p] == -1} {
45 if {[find_next_diff $current_diff_side $p {} {[^O]}]} {
46 next_diff $after
47 } else {
48 clear_diff
50 } else {
51 set save_pos [lindex [$ui_diff yview] 0]
52 show_diff $p $current_diff_side {} $save_pos $after
56 proc force_diff_encoding {enc} {
57 global current_diff_path
59 if {$current_diff_path ne {}} {
60 force_path_encoding $current_diff_path $enc
61 reshow_diff
65 proc handle_empty_diff {} {
66 global current_diff_path file_states file_lists
67 global diff_empty_count
69 set path $current_diff_path
70 set s $file_states($path)
71 if {[lindex $s 0] ne {_M} || [has_textconv $path]} return
73 # Prevent infinite rescan loops
74 incr diff_empty_count
75 if {$diff_empty_count > 1} return
77 #Removed info popup, it is useless since it states that an automatic
78 #resync will be started and there is no way to stop the resync.
80 clear_diff
81 display_file $path __
82 rescan ui_ready 0
85 proc show_diff {path w {lno {}} {scroll_pos {}} {callback {}}} {
86 global file_states file_lists
87 global is_3way_diff is_conflict_diff diff_active repo_config
88 global ui_diff ui_index ui_workdir
89 global current_diff_path current_diff_side current_diff_header
90 global current_diff_queue
92 if {$diff_active || ![lock_index read]} return
94 clear_diff
95 if {$lno == {}} {
96 set lno [lsearch -sorted -exact $file_lists($w) $path]
97 if {$lno >= 0} {
98 incr lno
101 if {$lno >= 1} {
102 $w tag add in_diff $lno.0 [expr {$lno + 1}].0
103 $w see $lno.0
106 set s $file_states($path)
107 set m [lindex $s 0]
108 set is_conflict_diff 0
109 set current_diff_path $path
110 set current_diff_side $w
111 set current_diff_queue {}
112 ui_status [mc "Loading diff of %s..." [escape_path $path]]
114 set cont_info [list $scroll_pos $callback]
116 apply_tab_size 0
118 if {[string first {U} $m] >= 0} {
119 merge_load_stages $path [list show_unmerged_diff $cont_info]
120 } elseif {$m eq {_O}} {
121 show_other_diff $path $w $m $cont_info
122 } else {
123 start_show_diff $cont_info
127 proc show_unmerged_diff {cont_info} {
128 global current_diff_path current_diff_side
129 global merge_stages ui_diff is_conflict_diff
130 global current_diff_queue
132 if {$merge_stages(2) eq {}} {
133 set is_conflict_diff 1
134 lappend current_diff_queue \
135 [list [mc "LOCAL: deleted\nREMOTE:\n"] d= \
136 [list ":1:$current_diff_path" ":3:$current_diff_path"]]
137 } elseif {$merge_stages(3) eq {}} {
138 set is_conflict_diff 1
139 lappend current_diff_queue \
140 [list [mc "REMOTE: deleted\nLOCAL:\n"] d= \
141 [list ":1:$current_diff_path" ":2:$current_diff_path"]]
142 } elseif {[lindex $merge_stages(1) 0] eq {120000}
143 || [lindex $merge_stages(2) 0] eq {120000}
144 || [lindex $merge_stages(3) 0] eq {120000}} {
145 set is_conflict_diff 1
146 lappend current_diff_queue \
147 [list [mc "LOCAL:\n"] d= \
148 [list ":1:$current_diff_path" ":2:$current_diff_path"]]
149 lappend current_diff_queue \
150 [list [mc "REMOTE:\n"] d= \
151 [list ":1:$current_diff_path" ":3:$current_diff_path"]]
152 } else {
153 start_show_diff $cont_info
154 return
157 advance_diff_queue $cont_info
160 proc advance_diff_queue {cont_info} {
161 global current_diff_queue ui_diff
163 set item [lindex $current_diff_queue 0]
164 set current_diff_queue [lrange $current_diff_queue 1 end]
166 $ui_diff conf -state normal
167 $ui_diff insert end [lindex $item 0] [lindex $item 1]
168 $ui_diff conf -state disabled
170 start_show_diff $cont_info [lindex $item 2]
173 proc show_other_diff {path w m cont_info} {
174 global file_states file_lists
175 global is_3way_diff diff_active repo_config
176 global ui_diff ui_index ui_workdir
177 global current_diff_path current_diff_side current_diff_header
179 # - Git won't give us the diff, there's nothing to compare to!
181 if {$m eq {_O}} {
182 set max_sz 100000
183 set type unknown
184 if {[catch {
185 set type [file type $path]
186 switch -- $type {
187 directory {
188 set type submodule
189 set content {}
190 set sz 0
192 link {
193 set content [file readlink $path]
194 set sz [string length $content]
196 file {
197 set fd [open $path r]
198 fconfigure $fd \
199 -eofchar {} \
200 -encoding [get_path_encoding $path]
201 set content [read $fd $max_sz]
202 close $fd
203 set sz [file size $path]
205 default {
206 error "'$type' not supported"
209 } err ]} {
210 set diff_active 0
211 unlock_index
212 ui_status [mc "Unable to display %s" [escape_path $path]]
213 error_popup [strcat [mc "Error loading file:"] "\n\n$err"]
214 return
216 $ui_diff conf -state normal
217 if {$type eq {submodule}} {
218 $ui_diff insert end [append \
219 "* " \
220 [mc "Git Repository (subproject)"] \
221 "\n"] d_info
222 } elseif {![catch {set type [exec file $path]}]} {
223 set n [string length $path]
224 if {[string equal -length $n $path $type]} {
225 set type [string range $type $n end]
226 regsub {^:?\s*} $type {} type
228 $ui_diff insert end "* $type\n" d_info
230 if {[string first "\0" $content] != -1} {
231 $ui_diff insert end \
232 [mc "* Binary file (not showing content)."] \
233 d_info
234 } else {
235 if {$sz > $max_sz} {
236 $ui_diff insert end [mc \
237 "* Untracked file is %d bytes.
238 * Showing only first %d bytes.
239 " $sz $max_sz] d_info
241 $ui_diff insert end $content
242 if {$sz > $max_sz} {
243 $ui_diff insert end [mc "
244 * Untracked file clipped here by %s.
245 * To see the entire file, use an external editor.
246 " [appname]] d_info
249 $ui_diff conf -state disabled
250 set diff_active 0
251 unlock_index
252 set scroll_pos [lindex $cont_info 0]
253 if {$scroll_pos ne {}} {
254 update
255 $ui_diff yview moveto $scroll_pos
257 ui_ready
258 set callback [lindex $cont_info 1]
259 if {$callback ne {}} {
260 eval $callback
262 return
266 proc get_conflict_marker_size {path} {
267 set size 7
268 catch {
269 set fd_rc [eval [list git_read check-attr "conflict-marker-size" -- $path]]
270 set ret [gets $fd_rc line]
271 close $fd_rc
272 if {$ret > 0} {
273 regexp {.*: conflict-marker-size: (\d+)$} $line line size
276 return $size
279 proc start_show_diff {cont_info {add_opts {}}} {
280 global file_states file_lists
281 global is_3way_diff is_submodule_diff diff_active repo_config
282 global ui_diff ui_index ui_workdir
283 global current_diff_path current_diff_side current_diff_header
285 set path $current_diff_path
286 set w $current_diff_side
288 set s $file_states($path)
289 set m [lindex $s 0]
290 set is_3way_diff 0
291 set is_submodule_diff 0
292 set diff_active 1
293 set current_diff_header {}
294 set conflict_size [get_conflict_marker_size $path]
296 set cmd [list]
297 if {$w eq $ui_index} {
298 lappend cmd diff-index
299 lappend cmd --cached
300 if {[git-version >= "1.7.2"]} {
301 lappend cmd --ignore-submodules=dirty
303 } elseif {$w eq $ui_workdir} {
304 if {[string first {U} $m] >= 0} {
305 lappend cmd diff
306 } else {
307 lappend cmd diff-files
310 if {![is_config_false gui.textconv] && [git-version >= 1.6.1]} {
311 lappend cmd --textconv
314 if {[string match {160000 *} [lindex $s 2]]
315 || [string match {160000 *} [lindex $s 3]]} {
316 set is_submodule_diff 1
318 if {[git-version >= "1.6.6"]} {
319 lappend cmd --submodule
323 lappend cmd -p
324 lappend cmd --color
325 set cmd [concat $cmd $repo_config(gui.diffopts)]
326 if {$repo_config(gui.diffcontext) >= 1} {
327 lappend cmd "-U$repo_config(gui.diffcontext)"
329 if {$w eq $ui_index} {
330 lappend cmd [PARENT]
332 if {$add_opts ne {}} {
333 eval lappend cmd $add_opts
334 } else {
335 lappend cmd --
336 lappend cmd $path
339 if {$is_submodule_diff && [git-version < "1.6.6"]} {
340 if {$w eq $ui_index} {
341 set cmd [list submodule summary --cached -- $path]
342 } else {
343 set cmd [list submodule summary --files -- $path]
347 if {[catch {set fd [eval git_read --nice $cmd]} err]} {
348 set diff_active 0
349 unlock_index
350 ui_status [mc "Unable to display %s" [escape_path $path]]
351 error_popup [strcat [mc "Error loading diff:"] "\n\n$err"]
352 return
355 set ::current_diff_inheader 1
356 fconfigure $fd \
357 -blocking 0 \
358 -encoding [get_path_encoding $path] \
359 -translation lf
360 fileevent $fd readable [list read_diff $fd $conflict_size $cont_info]
363 proc parse_color_line {line} {
364 set start 0
365 set result ""
366 set markup [list]
367 set regexp {\033\[((?:\d+;)*\d+)?m}
368 set need_reset 0
369 while {[regexp -indices -start $start $regexp $line match code]} {
370 foreach {begin end} $match break
371 append result [string range $line $start [expr {$begin - 1}]]
372 set pos [string length $result]
373 set col [eval [linsert $code 0 string range $line]]
374 set start [incr end]
375 if {$col eq "0" || $col eq ""} {
376 if {!$need_reset} continue
377 set need_reset 0
378 } else {
379 set need_reset 1
381 lappend markup $pos $col
383 append result [string range $line $start end]
384 if {[llength $markup] < 4} {set markup {}}
385 return [list $result $markup]
388 proc read_diff {fd conflict_size cont_info} {
389 global ui_diff diff_active is_submodule_diff
390 global is_3way_diff is_conflict_diff current_diff_header
391 global current_diff_queue
392 global diff_empty_count
394 $ui_diff conf -state normal
395 while {[gets $fd line] >= 0} {
396 foreach {line markup} [parse_color_line $line] break
397 set line [string map {\033 ^} $line]
399 set tags {}
401 # -- Check for start of diff header.
402 if { [string match {diff --git *} $line]
403 || [string match {diff --cc *} $line]
404 || [string match {diff --combined *} $line]} {
405 set ::current_diff_inheader 1
408 # -- Check for end of diff header (any hunk line will do this).
410 if {[regexp {^@@+ } $line]} {set ::current_diff_inheader 0}
412 # -- Automatically detect if this is a 3 way diff.
414 if {[string match {@@@ *} $line]} {
415 set is_3way_diff 1
416 apply_tab_size 1
419 if {$::current_diff_inheader} {
421 # -- These two lines stop a diff header and shouldn't be in there
422 if { [string match {Binary files * and * differ} $line]
423 || [regexp {^\* Unmerged path } $line]} {
424 set ::current_diff_inheader 0
425 } else {
426 append current_diff_header $line "\n"
429 # -- Cleanup uninteresting diff header lines.
431 if { [string match {diff --git *} $line]
432 || [string match {diff --cc *} $line]
433 || [string match {diff --combined *} $line]
434 || [string match {--- *} $line]
435 || [string match {+++ *} $line]
436 || [string match {index *} $line]} {
437 continue
440 # -- Name it symlink, not 120000
441 # Note, that the original line is in $current_diff_header
442 regsub {^(deleted|new) file mode 120000} $line {\1 symlink} line
444 } elseif { $line eq {\ No newline at end of file}} {
445 # -- Handle some special lines
446 } elseif {$is_3way_diff} {
447 set op [string range $line 0 1]
448 switch -- $op {
449 { } {set tags {}}
450 {@@} {set tags d_@}
451 { +} {set tags d_s+}
452 { -} {set tags d_s-}
453 {+ } {set tags d_+s}
454 {- } {set tags d_-s}
455 {--} {set tags d_--}
456 {++} {
457 set regexp [string map [list %conflict_size $conflict_size]\
458 {^\+\+([<>=]){%conflict_size}(?: |$)}]
459 if {[regexp $regexp $line _g op]} {
460 set is_conflict_diff 1
461 set line [string replace $line 0 1 { }]
462 set tags d$op
463 } else {
464 set tags d_++
467 default {
468 puts "error: Unhandled 3 way diff marker: {$op}"
469 set tags {}
472 } elseif {$is_submodule_diff} {
473 if {$line == ""} continue
474 if {[regexp {^Submodule } $line]} {
475 set tags d_info
476 } elseif {[regexp {^\* } $line]} {
477 set line [string replace $line 0 1 {Submodule }]
478 set tags d_info
479 } else {
480 set op [string range $line 0 2]
481 switch -- $op {
482 { <} {set tags d_-}
483 { >} {set tags d_+}
484 { W} {set tags {}}
485 default {
486 puts "error: Unhandled submodule diff marker: {$op}"
487 set tags {}
491 } else {
492 set op [string index $line 0]
493 switch -- $op {
494 { } {set tags {}}
495 {@} {set tags d_@}
496 {-} {set tags d_-}
497 {+} {
498 set regexp [string map [list %conflict_size $conflict_size]\
499 {^\+([<>=]){%conflict_size}(?: |$)}]
500 if {[regexp $regexp $line _g op]} {
501 set is_conflict_diff 1
502 set tags d$op
503 } else {
504 set tags d_+
507 default {
508 puts "error: Unhandled 2 way diff marker: {$op}"
509 set tags {}
513 set mark [$ui_diff index "end - 1 line linestart"]
514 $ui_diff insert end $line $tags
515 if {[string index $line end] eq "\r"} {
516 $ui_diff tag add d_cr {end - 2c}
518 $ui_diff insert end "\n" $tags
520 foreach {posbegin colbegin posend colend} $markup {
521 set prefix clr
522 foreach style [lsort -integer [split $colbegin ";"]] {
523 if {$style eq "7"} {append prefix i; continue}
524 if {$style != 4 && ($style < 30 || $style > 47)} {continue}
525 set a "$mark linestart + $posbegin chars"
526 set b "$mark linestart + $posend chars"
527 catch {$ui_diff tag add $prefix$style $a $b}
531 $ui_diff conf -state disabled
533 if {[eof $fd]} {
534 close $fd
536 if {$current_diff_queue ne {}} {
537 advance_diff_queue $cont_info
538 return
541 set diff_active 0
542 unlock_index
543 set scroll_pos [lindex $cont_info 0]
544 if {$scroll_pos ne {}} {
545 update
546 $ui_diff yview moveto $scroll_pos
548 ui_ready
550 if {[$ui_diff index end] eq {2.0}} {
551 handle_empty_diff
552 } else {
553 set diff_empty_count 0
556 set callback [lindex $cont_info 1]
557 if {$callback ne {}} {
558 eval $callback
563 proc apply_hunk {x y} {
564 global current_diff_path current_diff_header current_diff_side
565 global ui_diff ui_index file_states
567 if {$current_diff_path eq {} || $current_diff_header eq {}} return
568 if {![lock_index apply_hunk]} return
570 set apply_cmd {apply --cached --whitespace=nowarn}
571 set mi [lindex $file_states($current_diff_path) 0]
572 if {$current_diff_side eq $ui_index} {
573 set failed_msg [mc "Failed to unstage selected hunk."]
574 lappend apply_cmd --reverse
575 if {[string index $mi 0] ne {M}} {
576 unlock_index
577 return
579 } else {
580 set failed_msg [mc "Failed to stage selected hunk."]
581 if {[string index $mi 1] ne {M}} {
582 unlock_index
583 return
587 set s_lno [lindex [split [$ui_diff index @$x,$y] .] 0]
588 set s_lno [$ui_diff search -backwards -regexp ^@@ $s_lno.0 0.0]
589 if {$s_lno eq {}} {
590 unlock_index
591 return
594 set e_lno [$ui_diff search -forwards -regexp ^@@ "$s_lno + 1 lines" end]
595 if {$e_lno eq {}} {
596 set e_lno end
599 if {[catch {
600 set enc [get_path_encoding $current_diff_path]
601 set p [eval git_write $apply_cmd]
602 fconfigure $p -translation binary -encoding $enc
603 puts -nonewline $p $current_diff_header
604 puts -nonewline $p [$ui_diff get $s_lno $e_lno]
605 close $p} err]} {
606 error_popup [append $failed_msg "\n\n$err"]
607 unlock_index
608 return
611 $ui_diff conf -state normal
612 $ui_diff delete $s_lno $e_lno
613 $ui_diff conf -state disabled
615 if {[$ui_diff get 1.0 end] eq "\n"} {
616 set o _
617 } else {
618 set o ?
621 if {$current_diff_side eq $ui_index} {
622 set mi ${o}M
623 } elseif {[string index $mi 0] eq {_}} {
624 set mi M$o
625 } else {
626 set mi ?$o
628 unlock_index
629 display_file $current_diff_path $mi
630 # This should trigger shift to the next changed file
631 if {$o eq {_}} {
632 reshow_diff
636 proc apply_range_or_line {x y} {
637 global current_diff_path current_diff_header current_diff_side
638 global ui_diff ui_index file_states
640 set selected [$ui_diff tag nextrange sel 0.0]
642 if {$selected == {}} {
643 set first [$ui_diff index "@$x,$y"]
644 set last $first
645 } else {
646 set first [lindex $selected 0]
647 set last [lindex $selected 1]
650 set first_l [$ui_diff index "$first linestart"]
651 set last_l [$ui_diff index "$last lineend"]
653 if {$current_diff_path eq {} || $current_diff_header eq {}} return
654 if {![lock_index apply_hunk]} return
656 set apply_cmd {apply --cached --whitespace=nowarn}
657 set mi [lindex $file_states($current_diff_path) 0]
658 if {$current_diff_side eq $ui_index} {
659 set failed_msg [mc "Failed to unstage selected line."]
660 set to_context {+}
661 lappend apply_cmd --reverse
662 if {[string index $mi 0] ne {M}} {
663 unlock_index
664 return
666 } else {
667 set failed_msg [mc "Failed to stage selected line."]
668 set to_context {-}
669 if {[string index $mi 1] ne {M}} {
670 unlock_index
671 return
675 set wholepatch {}
677 while {$first_l < $last_l} {
678 set i_l [$ui_diff search -backwards -regexp ^@@ $first_l 0.0]
679 if {$i_l eq {}} {
680 # If there's not a @@ above, then the selected range
681 # must have come before the first_l @@
682 set i_l [$ui_diff search -regexp ^@@ $first_l $last_l]
684 if {$i_l eq {}} {
685 unlock_index
686 return
688 # $i_l is now at the beginning of a line
690 # pick start line number from hunk header
691 set hh [$ui_diff get $i_l "$i_l + 1 lines"]
692 set hh [lindex [split $hh ,] 0]
693 set hln [lindex [split $hh -] 1]
695 # There is a special situation to take care of. Consider this
696 # hunk:
698 # @@ -10,4 +10,4 @@
699 # context before
700 # -old 1
701 # -old 2
702 # +new 1
703 # +new 2
704 # context after
706 # We used to keep the context lines in the order they appear in
707 # the hunk. But then it is not possible to correctly stage only
708 # "-old 1" and "+new 1" - it would result in this staged text:
710 # context before
711 # old 2
712 # new 1
713 # context after
715 # (By symmetry it is not possible to *un*stage "old 2" and "new
716 # 2".)
718 # We resolve the problem by introducing an asymmetry, namely,
719 # when a "+" line is *staged*, it is moved in front of the
720 # context lines that are generated from the "-" lines that are
721 # immediately before the "+" block. That is, we construct this
722 # patch:
724 # @@ -10,4 +10,5 @@
725 # context before
726 # +new 1
727 # old 1
728 # old 2
729 # context after
731 # But we do *not* treat "-" lines that are *un*staged in a
732 # special way.
734 # With this asymmetry it is possible to stage the change "old
735 # 1" -> "new 1" directly, and to stage the change "old 2" ->
736 # "new 2" by first staging the entire hunk and then unstaging
737 # the change "old 1" -> "new 1".
739 # Applying multiple lines adds complexity to the special
740 # situation. The pre_context must be moved after the entire
741 # first block of consecutive staged "+" lines, so that
742 # staging both additions gives the following patch:
744 # @@ -10,4 +10,6 @@
745 # context before
746 # +new 1
747 # +new 2
748 # old 1
749 # old 2
750 # context after
752 # This is non-empty if and only if we are _staging_ changes;
753 # then it accumulates the consecutive "-" lines (after
754 # converting them to context lines) in order to be moved after
755 # "+" change lines.
756 set pre_context {}
758 set n 0
759 set m 0
760 set i_l [$ui_diff index "$i_l + 1 lines"]
761 set patch {}
762 while {[$ui_diff compare $i_l < "end - 1 chars"] &&
763 [$ui_diff get $i_l "$i_l + 2 chars"] ne {@@}} {
764 set next_l [$ui_diff index "$i_l + 1 lines"]
765 set c1 [$ui_diff get $i_l]
766 if {[$ui_diff compare $first_l <= $i_l] &&
767 [$ui_diff compare $i_l < $last_l] &&
768 ($c1 eq {-} || $c1 eq {+})} {
769 # a line to stage/unstage
770 set ln [$ui_diff get $i_l $next_l]
771 if {$c1 eq {-}} {
772 set n [expr $n+1]
773 set patch "$patch$pre_context$ln"
774 set pre_context {}
775 } else {
776 set m [expr $m+1]
777 set patch "$patch$ln"
779 } elseif {$c1 ne {-} && $c1 ne {+}} {
780 # context line
781 set ln [$ui_diff get $i_l $next_l]
782 set patch "$patch$pre_context$ln"
783 # Skip the "\ No newline at end of
784 # file". Depending on the locale setting
785 # we don't know what this line looks
786 # like exactly. The only thing we do
787 # know is that it starts with "\ "
788 if {![string match {\\ *} $ln]} {
789 set n [expr $n+1]
790 set m [expr $m+1]
792 set pre_context {}
793 } elseif {$c1 eq $to_context} {
794 # turn change line into context line
795 set ln [$ui_diff get "$i_l + 1 chars" $next_l]
796 if {$c1 eq {-}} {
797 set pre_context "$pre_context $ln"
798 } else {
799 set patch "$patch $ln"
801 set n [expr $n+1]
802 set m [expr $m+1]
803 } else {
804 # a change in the opposite direction of
805 # to_context which is outside the range of
806 # lines to apply.
807 set patch "$patch$pre_context"
808 set pre_context {}
810 set i_l $next_l
812 set patch "$patch$pre_context"
813 set wholepatch "$wholepatch@@ -$hln,$n +$hln,$m @@\n$patch"
814 set first_l [$ui_diff index "$next_l + 1 lines"]
817 if {[catch {
818 set enc [get_path_encoding $current_diff_path]
819 set p [eval git_write $apply_cmd]
820 fconfigure $p -translation binary -encoding $enc
821 puts -nonewline $p $current_diff_header
822 puts -nonewline $p $wholepatch
823 close $p} err]} {
824 error_popup [append $failed_msg "\n\n$err"]
827 unlock_index