Git fork
at reftables-rust 877 lines 23 kB view raw
1# git-gui diff viewer 2# Copyright (C) 2006, 2007 Shawn Pearce 3 4proc apply_tab_size {{firsttab {}}} { 5 global repo_config ui_diff 6 7 set w [font measure font_diff "0"] 8 if {$firsttab != 0} { 9 $ui_diff configure -tabs [list [expr {$firsttab * $w}] [expr {($firsttab + $repo_config(gui.tabsize)) * $w}]] 10 } else { 11 $ui_diff configure -tabs [expr {$repo_config(gui.tabsize) * $w}] 12 } 13} 14 15proc clear_diff {} { 16 global ui_diff current_diff_path current_diff_header 17 global ui_index ui_workdir 18 19 $ui_diff conf -state normal 20 $ui_diff delete 0.0 end 21 $ui_diff conf -state disabled 22 23 set current_diff_path {} 24 set current_diff_header {} 25 26 $ui_index tag remove in_diff 0.0 end 27 $ui_workdir tag remove in_diff 0.0 end 28} 29 30proc reshow_diff {{after {}}} { 31 global file_states file_lists 32 global current_diff_path current_diff_side 33 global ui_diff 34 35 set p $current_diff_path 36 if {$p eq {}} { 37 # No diff is being shown. 38 } elseif {$current_diff_side eq {}} { 39 clear_diff 40 } elseif {[catch {set s $file_states($p)}] 41 || [lsearch -sorted -exact $file_lists($current_diff_side) $p] == -1} { 42 43 if {[find_next_diff $current_diff_side $p {} {[^O]}]} { 44 next_diff $after 45 } else { 46 clear_diff 47 } 48 } else { 49 set save_pos [lindex [$ui_diff yview] 0] 50 show_diff $p $current_diff_side {} $save_pos $after 51 } 52} 53 54proc force_diff_encoding {enc} { 55 global current_diff_path 56 57 if {$current_diff_path ne {}} { 58 force_path_encoding $current_diff_path $enc 59 reshow_diff 60 } 61} 62 63proc handle_empty_diff {} { 64 global current_diff_path file_states 65 global ui_diff 66 67 set path $current_diff_path 68 set s $file_states($path) 69 if {[lindex $s 0] ne {_M} || [has_textconv $path]} return 70 71 $ui_diff conf -state normal 72 $ui_diff insert end [mc "* No differences detected; stage the file to de-list it from Unstaged Changes.\n"] d_info 73 $ui_diff insert end [mc "* Click to find other files that may have the same state.\n"] d_rescan 74 $ui_diff conf -state disabled 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 apply_tab_size 0 109 110 if {[string first {U} $m] >= 0} { 111 merge_load_stages $path [list show_unmerged_diff $cont_info] 112 } elseif {$m eq {_O}} { 113 show_other_diff $path $w $m $cont_info 114 } else { 115 start_show_diff $cont_info 116 } 117 118 global current_diff_path selected_paths 119 set selected_paths($current_diff_path) 1 120} 121 122proc show_unmerged_diff {cont_info} { 123 global current_diff_path current_diff_side 124 global merge_stages ui_diff is_conflict_diff 125 global current_diff_queue 126 127 if {$merge_stages(2) eq {}} { 128 set is_conflict_diff 1 129 lappend current_diff_queue \ 130 [list [mc "LOCAL: deleted\nREMOTE:\n"] d= \ 131 [list ":1:$current_diff_path" ":3:$current_diff_path"]] 132 } elseif {$merge_stages(3) eq {}} { 133 set is_conflict_diff 1 134 lappend current_diff_queue \ 135 [list [mc "REMOTE: deleted\nLOCAL:\n"] d= \ 136 [list ":1:$current_diff_path" ":2:$current_diff_path"]] 137 } elseif {[lindex $merge_stages(1) 0] eq {120000} 138 || [lindex $merge_stages(2) 0] eq {120000} 139 || [lindex $merge_stages(3) 0] eq {120000}} { 140 set is_conflict_diff 1 141 lappend current_diff_queue \ 142 [list [mc "LOCAL:\n"] d= \ 143 [list ":1:$current_diff_path" ":2:$current_diff_path"]] 144 lappend current_diff_queue \ 145 [list [mc "REMOTE:\n"] d= \ 146 [list ":1:$current_diff_path" ":3:$current_diff_path"]] 147 } else { 148 start_show_diff $cont_info 149 return 150 } 151 152 advance_diff_queue $cont_info 153} 154 155proc advance_diff_queue {cont_info} { 156 global current_diff_queue ui_diff 157 158 set item [lindex $current_diff_queue 0] 159 set current_diff_queue [lrange $current_diff_queue 1 end] 160 161 $ui_diff conf -state normal 162 $ui_diff insert end [lindex $item 0] [lindex $item 1] 163 $ui_diff conf -state disabled 164 165 start_show_diff $cont_info [lindex $item 2] 166} 167 168proc show_other_diff {path w m cont_info} { 169 global file_states file_lists 170 global is_3way_diff diff_active repo_config 171 global ui_diff ui_index ui_workdir 172 global current_diff_path current_diff_side current_diff_header 173 174 # - Git won't give us the diff, there's nothing to compare to! 175 # 176 if {$m eq {_O}} { 177 set max_sz 100000 178 set type unknown 179 if {[catch { 180 set type [file type $path] 181 switch -- $type { 182 directory { 183 set type submodule 184 set content {} 185 set sz 0 186 } 187 link { 188 set content [file readlink $path] 189 set sz [string length $content] 190 } 191 file { 192 set fd [safe_open_file $path r] 193 fconfigure $fd \ 194 -encoding [get_path_encoding $path] 195 set content [read $fd $max_sz] 196 close $fd 197 set sz [file size $path] 198 } 199 default { 200 error "'$type' not supported" 201 } 202 } 203 } err ]} { 204 set diff_active 0 205 unlock_index 206 ui_status [mc "Unable to display %s" [escape_path $path]] 207 error_popup [strcat [mc "Error loading file:"] "\n\n$err"] 208 return 209 } 210 $ui_diff conf -state normal 211 if {$type eq {submodule}} { 212 $ui_diff insert end \ 213 "* [mc "Git Repository (subproject)"]\n" \ 214 d_info 215 } elseif {![catch {set type [safe_exec [list file $path]]}]} { 216 set n [string length $path] 217 if {[string equal -length $n $path $type]} { 218 set type [string range $type $n end] 219 regsub {^:?\s*} $type {} type 220 } 221 $ui_diff insert end "* $type\n" d_info 222 } 223 if {[string first "\0" $content] != -1} { 224 $ui_diff insert end \ 225 [mc "* Binary file (not showing content)."] \ 226 d_info 227 } else { 228 if {$sz > $max_sz} { 229 $ui_diff insert end [mc \ 230"* Untracked file is %d bytes. 231* Showing only first %d bytes. 232" $sz $max_sz] d_info 233 } 234 $ui_diff insert end $content 235 if {$sz > $max_sz} { 236 $ui_diff insert end [mc " 237* Untracked file clipped here by %s. 238* To see the entire file, use an external editor. 239" [appname]] d_info 240 } 241 } 242 $ui_diff conf -state disabled 243 set diff_active 0 244 unlock_index 245 set scroll_pos [lindex $cont_info 0] 246 if {$scroll_pos ne {}} { 247 update 248 $ui_diff yview moveto $scroll_pos 249 } 250 ui_ready 251 set callback [lindex $cont_info 1] 252 if {$callback ne {}} { 253 eval $callback 254 } 255 return 256 } 257} 258 259proc start_show_diff {cont_info {add_opts {}}} { 260 global file_states file_lists 261 global is_3way_diff is_submodule_diff diff_active repo_config 262 global ui_diff ui_index ui_workdir 263 global current_diff_path current_diff_side current_diff_header 264 265 set path $current_diff_path 266 set w $current_diff_side 267 268 set s $file_states($path) 269 set m [lindex $s 0] 270 set is_3way_diff 0 271 set is_submodule_diff 0 272 set diff_active 1 273 set current_diff_header {} 274 set conflict_size [gitattr $path conflict-marker-size 7] 275 276 set cmd [list] 277 if {$w eq $ui_index} { 278 lappend cmd diff-index 279 lappend cmd --cached 280 lappend cmd --ignore-submodules=dirty 281 } elseif {$w eq $ui_workdir} { 282 if {[string first {U} $m] >= 0} { 283 lappend cmd diff 284 } else { 285 lappend cmd diff-files 286 } 287 } 288 if {![is_config_false gui.textconv]} { 289 lappend cmd --textconv 290 } 291 292 if {[string match {160000 *} [lindex $s 2]] 293 || [string match {160000 *} [lindex $s 3]]} { 294 set is_submodule_diff 1 295 lappend cmd --submodule 296 } 297 298 lappend cmd -p 299 lappend cmd --color 300 set cmd [concat $cmd $repo_config(gui.diffopts)] 301 if {$repo_config(gui.diffcontext) >= 1} { 302 lappend cmd "-U$repo_config(gui.diffcontext)" 303 } 304 if {$w eq $ui_index} { 305 lappend cmd [PARENT] 306 } 307 if {$add_opts ne {}} { 308 eval lappend cmd $add_opts 309 } else { 310 lappend cmd -- 311 lappend cmd $path 312 } 313 314 if {[catch {set fd [git_read_nice $cmd]} err]} { 315 set diff_active 0 316 unlock_index 317 ui_status [mc "Unable to display %s" [escape_path $path]] 318 error_popup [strcat [mc "Error loading diff:"] "\n\n$err"] 319 return 320 } 321 322 set ::current_diff_inheader 1 323 # Detect pre-image lines of the diff3 conflict-style. They are just 324 # '++' lines which is not bijective. Thus, we need to maintain a state 325 # across lines. 326 set ::conflict_in_pre_image 0 327 328 # git-diff has eol==\n, \r if present is part of the text 329 fconfigure $fd \ 330 -blocking 0 \ 331 -encoding [get_path_encoding $path] \ 332 -translation lf 333 fileevent $fd readable [list read_diff $fd $conflict_size $cont_info] 334} 335 336proc parse_color_line {line} { 337 set start 0 338 set result "" 339 set markup [list] 340 set regexp {\033\[((?:\d+;)*\d+)?m} 341 set need_reset 0 342 while {[regexp -indices -start $start $regexp $line match code]} { 343 foreach {begin end} $match break 344 append result [string range $line $start [expr {$begin - 1}]] 345 set pos [string length $result] 346 set col [eval [linsert $code 0 string range $line]] 347 set start [incr end] 348 if {$col eq "0" || $col eq ""} { 349 if {!$need_reset} continue 350 set need_reset 0 351 } else { 352 set need_reset 1 353 } 354 lappend markup $pos $col 355 } 356 append result [string range $line $start end] 357 if {[llength $markup] < 4} {set markup {}} 358 return [list $result $markup] 359} 360 361proc read_diff {fd conflict_size cont_info} { 362 global ui_diff diff_active is_submodule_diff 363 global is_3way_diff is_conflict_diff current_diff_header 364 global current_diff_queue 365 366 $ui_diff conf -state normal 367 while {[gets $fd line] >= 0} { 368 foreach {line markup} [parse_color_line $line] break 369 set line [string map {\033 ^} $line] 370 371 set tags {} 372 373 # -- Check for start of diff header. 374 if { [string match {diff --git *} $line] 375 || [string match {diff --cc *} $line] 376 || [string match {diff --combined *} $line]} { 377 set ::current_diff_inheader 1 378 } 379 380 # -- Check for end of diff header (any hunk line will do this). 381 # 382 if {[regexp {^@@+ } $line]} {set ::current_diff_inheader 0} 383 384 # -- Automatically detect if this is a 3 way diff. 385 # 386 if {[string match {@@@ *} $line]} { 387 set is_3way_diff 1 388 apply_tab_size 1 389 } 390 391 if {$::current_diff_inheader} { 392 393 # -- These two lines stop a diff header and shouldn't be in there 394 if { [string match {Binary files * and * differ} $line] 395 || [regexp {^\* Unmerged path } $line]} { 396 set ::current_diff_inheader 0 397 } else { 398 append current_diff_header $line "\n" 399 } 400 401 # -- Cleanup uninteresting diff header lines. 402 # 403 if { [string match {diff --git *} $line] 404 || [string match {diff --cc *} $line] 405 || [string match {diff --combined *} $line] 406 || [string match {--- *} $line] 407 || [string match {+++ *} $line] 408 || [string match {index *} $line]} { 409 continue 410 } 411 412 # -- Name it symlink, not 120000 413 # Note, that the original line is in $current_diff_header 414 regsub {^(deleted|new) file mode 120000} $line {\1 symlink} line 415 416 } elseif { $line eq {\ No newline at end of file}} { 417 # -- Handle some special lines 418 } elseif {$is_3way_diff} { 419 set op [string range $line 0 1] 420 switch -- $op { 421 { } {set tags {}} 422 {@@} {set tags d_@} 423 { +} {set tags d_s+} 424 { -} {set tags d_s-} 425 {+ } {set tags d_+s} 426 {- } {set tags d_-s} 427 {--} {set tags d_--} 428 {++} { 429 set regexp [string map [list %conflict_size $conflict_size]\ 430 {^\+\+([<>=|]){%conflict_size}(?: |$)}] 431 if {[regexp $regexp $line _g op]} { 432 set is_conflict_diff 1 433 set line [string replace $line 0 1 { }] 434 set tags d$op 435 436 # The ||| conflict-marker marks the start of the pre-image. 437 # All those lines are also prefixed with '++'. Thus we need 438 # to maintain this state. 439 set ::conflict_in_pre_image [expr {$op eq {|}}] 440 } elseif {$::conflict_in_pre_image} { 441 # This is a pre-image line. It is the one which both sides 442 # are based on. As it has also the '++' line start, it is 443 # normally shown as 'added'. Invert this to '--' to make 444 # it a 'removed' line. 445 set line [string replace $line 0 1 {--}] 446 set tags d_-- 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 } 537 538 set callback [lindex $cont_info 1] 539 if {$callback ne {}} { 540 eval $callback 541 } 542 } 543} 544 545proc apply_or_revert_hunk {x y revert} { 546 global current_diff_path current_diff_header current_diff_side 547 global ui_diff ui_index file_states last_revert last_revert_enc 548 549 if {$current_diff_path eq {} || $current_diff_header eq {}} return 550 if {![lock_index apply_hunk]} return 551 552 set apply_cmd {apply --whitespace=nowarn} 553 set mi [lindex $file_states($current_diff_path) 0] 554 if {$current_diff_side eq $ui_index} { 555 set failed_msg [mc "Failed to unstage selected hunk."] 556 lappend apply_cmd --reverse --cached 557 if {[string index $mi 0] ne {M}} { 558 unlock_index 559 return 560 } 561 } else { 562 if {$revert} { 563 set failed_msg [mc "Failed to revert selected hunk."] 564 lappend apply_cmd --reverse 565 } else { 566 set failed_msg [mc "Failed to stage selected hunk."] 567 lappend apply_cmd --cached 568 } 569 570 if {[string index $mi 1] ne {M}} { 571 unlock_index 572 return 573 } 574 } 575 576 set s_lno [lindex [split [$ui_diff index @$x,$y] .] 0] 577 set s_lno [$ui_diff search -backwards -regexp ^@@ $s_lno.0 0.0] 578 if {$s_lno eq {}} { 579 unlock_index 580 return 581 } 582 583 set e_lno [$ui_diff search -forwards -regexp ^@@ "$s_lno + 1 lines" end] 584 if {$e_lno eq {}} { 585 set e_lno end 586 } 587 588 set wholepatch "$current_diff_header[$ui_diff get $s_lno $e_lno]" 589 590 if {[catch { 591 set enc [get_path_encoding $current_diff_path] 592 set p [git_write $apply_cmd] 593 fconfigure $p -translation binary -encoding $enc 594 puts -nonewline $p $wholepatch 595 close $p} err]} { 596 error_popup "$failed_msg\n\n$err" 597 unlock_index 598 return 599 } 600 601 if {$revert} { 602 # Save a copy of this patch for undoing reverts. 603 set last_revert $wholepatch 604 set last_revert_enc $enc 605 } 606 607 $ui_diff conf -state normal 608 $ui_diff delete $s_lno $e_lno 609 $ui_diff conf -state disabled 610 611 # Check if the hunk was the last one in the file. 612 if {[$ui_diff get 1.0 end] eq "\n"} { 613 set o _ 614 } else { 615 set o ? 616 } 617 618 # Update the status flags. 619 if {$revert} { 620 set mi [string index $mi 0]$o 621 } elseif {$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 627 } 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 633 } 634} 635 636proc apply_or_revert_range_or_line {x y revert} { 637 global current_diff_path current_diff_header current_diff_side 638 global ui_diff ui_index file_states last_revert 639 640 set selected [$ui_diff tag nextrange sel 0.0] 641 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] 648 } 649 650 set first_l [$ui_diff index "$first linestart"] 651 set last_l [$ui_diff index "$last lineend"] 652 653 if {$current_diff_path eq {} || $current_diff_header eq {}} return 654 if {![lock_index apply_hunk]} return 655 656 set apply_cmd {apply --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 --cached 662 if {[string index $mi 0] ne {M}} { 663 unlock_index 664 return 665 } 666 } else { 667 if {$revert} { 668 set failed_msg [mc "Failed to revert selected line."] 669 set to_context {+} 670 lappend apply_cmd --reverse 671 } else { 672 set failed_msg [mc "Failed to stage selected line."] 673 set to_context {-} 674 lappend apply_cmd --cached 675 } 676 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 set hln [lindex [split $hln " "] 0] 703 704 # There is a special situation to take care of. Consider this 705 # hunk: 706 # 707 # @@ -10,4 +10,4 @@ 708 # context before 709 # -old 1 710 # -old 2 711 # +new 1 712 # +new 2 713 # context after 714 # 715 # We used to keep the context lines in the order they appear in 716 # the hunk. But then it is not possible to correctly stage only 717 # "-old 1" and "+new 1" - it would result in this staged text: 718 # 719 # context before 720 # old 2 721 # new 1 722 # context after 723 # 724 # (By symmetry it is not possible to *un*stage "old 2" and "new 725 # 2".) 726 # 727 # We resolve the problem by introducing an asymmetry, namely, 728 # when a "+" line is *staged*, it is moved in front of the 729 # context lines that are generated from the "-" lines that are 730 # immediately before the "+" block. That is, we construct this 731 # patch: 732 # 733 # @@ -10,4 +10,5 @@ 734 # context before 735 # +new 1 736 # old 1 737 # old 2 738 # context after 739 # 740 # But we do *not* treat "-" lines that are *un*staged in a 741 # special way. 742 # 743 # With this asymmetry it is possible to stage the change "old 744 # 1" -> "new 1" directly, and to stage the change "old 2" -> 745 # "new 2" by first staging the entire hunk and then unstaging 746 # the change "old 1" -> "new 1". 747 # 748 # Applying multiple lines adds complexity to the special 749 # situation. The pre_context must be moved after the entire 750 # first block of consecutive staged "+" lines, so that 751 # staging both additions gives the following patch: 752 # 753 # @@ -10,4 +10,6 @@ 754 # context before 755 # +new 1 756 # +new 2 757 # old 1 758 # old 2 759 # context after 760 761 # This is non-empty if and only if we are _staging_ changes; 762 # then it accumulates the consecutive "-" lines (after 763 # converting them to context lines) in order to be moved after 764 # "+" change lines. 765 set pre_context {} 766 767 set n 0 768 set m 0 769 set i_l [$ui_diff index "$i_l + 1 lines"] 770 set patch {} 771 while {[$ui_diff compare $i_l < "end - 1 chars"] && 772 [$ui_diff get $i_l "$i_l + 2 chars"] ne {@@}} { 773 set next_l [$ui_diff index "$i_l + 1 lines"] 774 set c1 [$ui_diff get $i_l] 775 if {[$ui_diff compare $first_l <= $i_l] && 776 [$ui_diff compare $i_l < $last_l] && 777 ($c1 eq {-} || $c1 eq {+})} { 778 # a line to stage/unstage 779 set ln [$ui_diff get $i_l $next_l] 780 if {$c1 eq {-}} { 781 set n [expr $n+1] 782 set patch "$patch$pre_context$ln" 783 set pre_context {} 784 } else { 785 set m [expr $m+1] 786 set patch "$patch$ln" 787 } 788 } elseif {$c1 ne {-} && $c1 ne {+}} { 789 # context line 790 set ln [$ui_diff get $i_l $next_l] 791 set patch "$patch$pre_context$ln" 792 # Skip the "\ No newline at end of 793 # file". Depending on the locale setting 794 # we don't know what this line looks 795 # like exactly. The only thing we do 796 # know is that it starts with "\ " 797 if {![string match {\\ *} $ln]} { 798 set n [expr $n+1] 799 set m [expr $m+1] 800 } 801 set pre_context {} 802 } elseif {$c1 eq $to_context} { 803 # turn change line into context line 804 set ln [$ui_diff get "$i_l + 1 chars" $next_l] 805 if {$c1 eq {-}} { 806 set pre_context "$pre_context $ln" 807 } else { 808 set patch "$patch $ln" 809 } 810 set n [expr $n+1] 811 set m [expr $m+1] 812 } else { 813 # a change in the opposite direction of 814 # to_context which is outside the range of 815 # lines to apply. 816 set patch "$patch$pre_context" 817 set pre_context {} 818 } 819 set i_l $next_l 820 } 821 set patch "$patch$pre_context" 822 set wholepatch "$wholepatch@@ -$hln,$n +$hln,$m @@\n$patch" 823 set first_l [$ui_diff index "$next_l + 1 lines"] 824 } 825 826 if {[catch { 827 set enc [get_path_encoding $current_diff_path] 828 set p [git_write $apply_cmd] 829 fconfigure $p -translation binary -encoding $enc 830 puts -nonewline $p $current_diff_header 831 puts -nonewline $p $wholepatch 832 close $p} err]} { 833 error_popup "$failed_msg\n\n$err" 834 unlock_index 835 return 836 } 837 838 if {$revert} { 839 # Save a copy of this patch for undoing reverts. 840 set last_revert $current_diff_header$wholepatch 841 set last_revert_enc $enc 842 } 843 844 unlock_index 845} 846 847# Undo the last line/hunk reverted. When hunks and lines are reverted, a copy 848# of the diff applied is saved. Re-apply that diff to undo the revert. 849# 850# Right now, we only use a single variable to hold the copy, and not a 851# stack/deque for simplicity, so multiple undos are not possible. Maybe this 852# can be added if the need for something like this is felt in the future. 853proc undo_last_revert {} { 854 global last_revert current_diff_path current_diff_header 855 global last_revert_enc 856 857 if {$last_revert eq {}} return 858 if {![lock_index apply_hunk]} return 859 860 set apply_cmd {apply --whitespace=nowarn} 861 set failed_msg [mc "Failed to undo last revert."] 862 863 if {[catch { 864 set enc $last_revert_enc 865 set p [git_write $apply_cmd] 866 fconfigure $p -translation binary -encoding $enc 867 puts -nonewline $p $last_revert 868 close $p} err]} { 869 error_popup "$failed_msg\n\n$err" 870 unlock_index 871 return 872 } 873 874 set last_revert {} 875 876 unlock_index 877}