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