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