rc tools patch: "patch" command to apply selections in diffs to file
One of the features I miss most from Magit/Fugitive/Tig is to apply/revert/stage/unstage individual hunks or even exactly the selected line(s). This provides a much more convenient way of splitting changes than "git add/restore -p". Implement a "patch" command that applies the selected lines within a diff by piping them to the "patch" program. It can also feed other programs like "git apply" (see the next commit). Original discussion: https://discuss.kakoune.com/t/atomic-commits-in-kakoune/1446 Interestingly, :patch is defined outside the "patch" module. This is to make it readily available for interactive use. Putting it into the module does not save any work. I tentatively added a patch module anyway so we can explicitly declare this dependency.. although there is the argument that this is not really needed?
This commit is contained in:
parent
6a39ac224b
commit
8c0424b521
93
rc/tools/patch-range.pl
Executable file
93
rc/tools/patch-range.pl
Executable file
|
@ -0,0 +1,93 @@
|
||||||
|
#!/usr/bin/env perl
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
my $min_line = $ARGV[0];
|
||||||
|
shift @ARGV;
|
||||||
|
my $max_line = $ARGV[0];
|
||||||
|
shift @ARGV;
|
||||||
|
|
||||||
|
my $patch_cmd;
|
||||||
|
if (defined $ARGV[0] and $ARGV[0] =~ m{^[^-]}) {
|
||||||
|
$patch_cmd = "@ARGV";
|
||||||
|
} else {
|
||||||
|
$patch_cmd = "patch @ARGV";
|
||||||
|
}
|
||||||
|
my $reverse = grep /^(--reverse|-R)$/, @ARGV;
|
||||||
|
|
||||||
|
my $lineno = 0;
|
||||||
|
my $original = "";
|
||||||
|
my $wheat = "";
|
||||||
|
my $chaff = "";
|
||||||
|
my $state = undef;
|
||||||
|
my $hunk_wheat = undef;
|
||||||
|
my $hunk_chaff = undef;
|
||||||
|
my $hunk_header = undef;
|
||||||
|
|
||||||
|
sub compute_hunk_header {
|
||||||
|
my $original_header = shift;
|
||||||
|
my $hunk = shift;
|
||||||
|
my $old_lines = 0;
|
||||||
|
my $new_lines = 0;
|
||||||
|
for (split /\n/, $hunk) {
|
||||||
|
$old_lines++ if m{^[ -]};
|
||||||
|
$new_lines++ if m{^[ +]};
|
||||||
|
}
|
||||||
|
my $updated_header = $original_header =~ s/^@@ -(\d+),\d+\s+\+(\d+),\d+ @@(.*)/@@ -$1,$old_lines +$2,$new_lines @\@$3/mr;
|
||||||
|
return $updated_header;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub finish_hunk {
|
||||||
|
return unless defined $hunk_header;
|
||||||
|
if ($hunk_wheat =~ m{^[-+]}m) {
|
||||||
|
$wheat .= (compute_hunk_header $hunk_header, $hunk_wheat). $hunk_wheat;
|
||||||
|
}
|
||||||
|
$chaff .= (compute_hunk_header $hunk_header, $hunk_chaff) . $hunk_chaff;
|
||||||
|
$hunk_header = undef;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (<STDIN>) {
|
||||||
|
++$lineno;
|
||||||
|
$original .= $_;
|
||||||
|
if (m{^diff}) {
|
||||||
|
finish_hunk();
|
||||||
|
$state = "diff header";
|
||||||
|
}
|
||||||
|
if (m{^@@}) {
|
||||||
|
finish_hunk();
|
||||||
|
$state = "diff hunk";
|
||||||
|
$hunk_header = $_;
|
||||||
|
$hunk_wheat = "";
|
||||||
|
$hunk_chaff = "";
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
if ($state eq "diff header") {
|
||||||
|
$wheat .= $_;
|
||||||
|
$chaff .= $_;
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
my $include = m{^ } || ($lineno >= $min_line && $lineno <= $max_line);
|
||||||
|
if ($include) {
|
||||||
|
$hunk_wheat .= $_;
|
||||||
|
$hunk_chaff .= $_ if m{^ };
|
||||||
|
if ($reverse ? m{^[-]} : m{^\+}) {
|
||||||
|
$hunk_chaff .= " " . substr $_, 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($reverse ? m{^\+} : m{^-}) {
|
||||||
|
$hunk_wheat .= " " . substr $_, 1;
|
||||||
|
}
|
||||||
|
$hunk_chaff .= $_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finish_hunk();
|
||||||
|
|
||||||
|
open PATCH_COMMAND, "|-", "$patch_cmd 1>&2" or die "patch-range.pl: error running '$patch_cmd': $!";
|
||||||
|
print PATCH_COMMAND $wheat;
|
||||||
|
if (not close PATCH_COMMAND) {
|
||||||
|
print $original;
|
||||||
|
print STDERR "patch-range.pl: error running:\n" . "\$ $patch_cmd << EOF\n$wheat" . "EOF\n";
|
||||||
|
exit 1;
|
||||||
|
}
|
||||||
|
print $chaff;
|
66
rc/tools/patch.kak
Normal file
66
rc/tools/patch.kak
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
define-command patch -params .. -docstring %{
|
||||||
|
patch [<arguments>]: apply selections in diff to a file
|
||||||
|
|
||||||
|
Given some selections within a unified diff, apply the changed lines in
|
||||||
|
each selection by piping them to "patch <arguments> 1>&2"
|
||||||
|
(or "<arguments> 1>&2" if <arguments> starts with a non-option argument).
|
||||||
|
If successful, the in-buffer diff will be updated to reflect the applied
|
||||||
|
changes.
|
||||||
|
For selections that contain no newline, the entire enclosing diff hunk
|
||||||
|
is applied (unless the cursor is inside a diff header, in which case
|
||||||
|
the entire diff is applied).
|
||||||
|
To revert changes, <arguments> must contain "--reverse" or "-R".
|
||||||
|
} %exp{
|
||||||
|
evaluate-commands -draft -itersel -save-regs aefs|^ %%{
|
||||||
|
set-register f %val{source}
|
||||||
|
%{
|
||||||
|
try %{
|
||||||
|
execute-keys <a-k>\n<ret>
|
||||||
|
} catch %{
|
||||||
|
# The selection contains no newline.
|
||||||
|
execute-keys -save-regs '' Z
|
||||||
|
execute-keys <a-l><semicolon><a-?>^diff<ret>
|
||||||
|
try %{
|
||||||
|
execute-keys <a-k>^@@<ret>
|
||||||
|
# If the cursor is in a diff hunk, stage the entire hunk.
|
||||||
|
execute-keys z
|
||||||
|
execute-keys /.*?(?:(?=\n@@)|(?=\ndiff)|(?=\n\n)|\z)<ret>x<semicolon><a-?>^@@<ret>
|
||||||
|
} catch %{
|
||||||
|
# If the cursor is in a diff header, stage the entire diff.
|
||||||
|
execute-keys <a-semicolon>?.*?(?:(?=\ndiff)|(?=\n\n)|\z)<ret>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# We want to apply only the selected lines. Remember them.
|
||||||
|
execute-keys <a-:>
|
||||||
|
set-register s %val{selection_desc}
|
||||||
|
# Select forward until the end of the last hunk.
|
||||||
|
execute-keys H?.*?(?:(?=\n@@)|(?=\ndiff)|(?=\n\n)|\z)<ret>x
|
||||||
|
# Select backward to the beginning of the first hunk's diff header.
|
||||||
|
execute-keys <a-semicolon><a-L><a-?>^diff<ret>
|
||||||
|
# Move cursor to the beginning so we know the diff's offset within the buffer.
|
||||||
|
execute-keys <a-:><a-semicolon>
|
||||||
|
set-register a %arg{@}
|
||||||
|
set-register e nop
|
||||||
|
set-register | %{
|
||||||
|
# The selected range to apply.
|
||||||
|
IFS=' .,' read min_line _ max_line _ <<-EOF
|
||||||
|
$kak_reg_s
|
||||||
|
EOF
|
||||||
|
min_line=$((min_line - kak_cursor_line + 1))
|
||||||
|
max_line=$((max_line - kak_cursor_line + 1))
|
||||||
|
|
||||||
|
# Since registers are never empty, we get an empty arg even if
|
||||||
|
# there were no args. This does no harm because we pass it to
|
||||||
|
# a shell where it expands to nothing.
|
||||||
|
eval set -- $kak_quoted_reg_a
|
||||||
|
|
||||||
|
"${kak_reg_f%/*}/patch-range.pl" $min_line $max_line "$@" ||
|
||||||
|
echo >$kak_command_fifo "set-register e fail 'patch: failed to apply selections, see *debug* buffer'"
|
||||||
|
}
|
||||||
|
execute-keys |<ret>
|
||||||
|
%reg{e}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
provide-module patch %§§
|
Loading…
Reference in New Issue
Block a user